Add Jetty project to external/jetty.

Bug: 14441587
Change-Id: If3dfb53d94a1d098f2eb0422934b52d855cda6be
diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..3b09b70
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry excluding="org/eclipse/jetty/jmx/|org/eclipse/jetty/util/log/jmx/|org/eclipse/jetty/server/handler/jmx/|org/eclipse/jetty/server/jmx/|org/eclipse/jetty/server/session/jmx/|org/eclipse/jetty/servlet/jmx/|org/eclipse/jetty/security/SpnegoLoginService.java|org/eclipse/jetty/security/SpnegoUserIdentity.java|org/eclipse/jetty/security/SpnegoUserPrincipal.java|org/eclipse/jetty/server/session/JDBCSessionIdManager.java|org/eclipse/jetty/server/session/JDBCSessionManager.java|org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java|org/eclipse/jetty/util/preventers/AWTLeakPreventer.java" including="**/*.java" kind="src" path="src/java"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="lib" path="lib/javax.servlet-3.0.0.v201112011016.jar" sourcepath="lib/javax.servlet-3.0.0.v201112011016-sources.jar"/>
+	<classpathentry kind="lib" path="lib/slf4j-api-1.6.1.jar"/>
+	<classpathentry kind="lib" path="lib/slf4j-jdk14-1.6.1.jar"/>
+	<classpathentry kind="lib" path="lib/jetty-util-6.1.26.jar"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/.project b/.project
new file mode 100644
index 0000000..612c8cd
--- /dev/null
+++ b/.project
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+  <name>jetty</name>
+  <projects>
+  </projects>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..7341ab1
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..f7ed668
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,55 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+NON_ANDROID_SRC := \
+	src/java/org/eclipse/jetty/jmx/ConnectorServer.java \
+	src/java/org/eclipse/jetty/jmx/MBeanContainer.java \
+	src/java/org/eclipse/jetty/jmx/ObjectMBean.java \
+	src/java/org/eclipse/jetty/servlet/jmx/ServletMappingMBean.java \
+	src/java/org/eclipse/jetty/servlet/jmx/FilterMappingMBean.java \
+	src/java/org/eclipse/jetty/servlet/jmx/HolderMBean.java \
+	src/java/org/eclipse/jetty/util/log/jmx/LogMBean.java \
+	src/java/org/eclipse/jetty/server/jmx/ServerMBean.java \
+	src/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java \
+	src/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java \
+	src/java/org/eclipse/jetty/servlet/StatisticsServlet.java \
+	src/java/org/eclipse/jetty/server/session/jmx/AbstractSessionManagerMBean.java \
+	src/java/org/eclipse/jetty/security/SpnegoUserIdentity.java \
+	src/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java \
+	src/java/org/eclipse/jetty/security/SpnegoLoginService.java \
+	src/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java \
+	src/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java \
+	src/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java \
+	src/java/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java \
+	src/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java \
+	src/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java \
+	src/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
+
+LOCAL_SRC_FILES := $(filter-out $(NON_ANDROID_SRC), \
+	$(call all-java-files-under, src))
+
+LOCAL_MODULE := jetty
+LOCAL_MODULE_TAGS := optional
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	jetty-util \
+	servlet-api \
+	slf4j-api \
+	slf4j-jdk14
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+#############################################################
+# Pre-built dependency jars
+#############################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+	servlet-api:lib/javax.servlet-3.0.0.v201112011016.jar \
+	jetty-util:lib/jetty-util-6.1.26.jar \
+	slf4j-api:lib/slf4j-api-1.6.1.jar \
+	slf4j-jdk14:lib/slf4j-jdk14-1.6.1.jar \
+
+include $(BUILD_MULTI_PREBUILT)
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/MODULE_LICENSE_ECLIPSE1 b/MODULE_LICENSE_ECLIPSE1
new file mode 100644
index 0000000..79e486c
--- /dev/null
+++ b/MODULE_LICENSE_ECLIPSE1
@@ -0,0 +1,70 @@
+Eclipse Public License - v 1.0
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and
+b) in the case of each subsequent Contributor:
+i) changes to the Program, and
+ii) additions to the Program;
+where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
+b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
+c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
+d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+b) its license agreement:
+i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
+ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
+iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
+iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+b) a copy of this Agreement must be included with each copy of the Program.
+Contributors may not remove or alter any copyright notices contained within the Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.
diff --git a/MODULE_LICENSE_SLF4J b/MODULE_LICENSE_SLF4J
new file mode 100644
index 0000000..37050c9
--- /dev/null
+++ b/MODULE_LICENSE_SLF4J
@@ -0,0 +1,21 @@
+ Copyright (c) 2004-2013 QOS.ch
+ All rights reserved.
+
+ Permission is hereby granted, free  of charge, to any person obtaining
+ a  copy  of this  software  and  associated  documentation files  (the
+ "Software"), to  deal in  the Software without  restriction, including
+ without limitation  the rights to  use, copy, modify,  merge, publish,
+ distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ permit persons to whom the Software  is furnished to do so, subject to
+ the following conditions:
+ 
+ The  above  copyright  notice  and  this permission  notice  shall  be
+ included in all copies or substantial portions of the Software.
+ 
+ THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..66eeea2
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,56 @@
+==============================================================
+ Jetty Web Container
+ Copyright 1995-2014 Mort Bay Consulting Pty Ltd.
+==============================================================
+
+The Jetty Web Container is Copyright Mort Bay Consulting Pty Ltd
+unless otherwise noted.
+
+Jetty is dual licensed under both
+
+  * The Apache 2.0 License
+    http://www.apache.org/licenses/LICENSE-2.0.html
+
+      and
+
+  * The Eclipse Public 1.0 License
+    http://www.eclipse.org/legal/epl-v10.html
+
+Jetty may be distributed under either license.
+
+The javax.servlet package used was sourced from the Apache
+Software Foundation and is distributed under the apache 2.0
+license.
+
+The UnixCrypt.java code implements the one way cryptography used by
+Unix systems for simple password protection.  Copyright 1996 Aki Yoshida,
+modified April 2001  by Iris Van den Broeke, Daniel Deville.
+Permission to use, copy, modify and distribute UnixCrypt
+for non-commercial or commercial purposes and without fee is
+granted provided that the copyright notice appears in all copies.
+
+==============================================================
+ SLF4J
+ Copyright (c) 2004-2013 QOS.ch
+==============================================================
+ Copyright (c) 2004-2013 QOS.ch
+ All rights reserved.
+
+ Permission is hereby granted, free  of charge, to any person obtaining
+ a  copy  of this  software  and  associated  documentation files  (the
+ "Software"), to  deal in  the Software without  restriction, including
+ without limitation  the rights to  use, copy, modify,  merge, publish,
+ distribute,  sublicense, and/or sell  copies of  the Software,  and to
+ permit persons to whom the Software  is furnished to do so, subject to
+ the following conditions:
+ 
+ The  above  copyright  notice  and  this permission  notice  shall  be
+ included in all copies or substantial portions of the Software.
+ 
+ THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+ EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+ MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.android b/README.android
new file mode 100644
index 0000000..012a796
--- /dev/null
+++ b/README.android
@@ -0,0 +1,48 @@
+URL: http://www.eclipse.org/jetty/
+Version: 8.1.15.v20140411
+License: Apache 2 or Eclipse 1.0 (see NOTICE file for information on licensing for dependencies)
+
+Description
+-------------------
+Jetty is a lightweight, servlet container implementation in Java.
+
+The pom.xml_saved file is included as a reference for bringing down the complete, original source
+code and dependencies using maven. Note, the file structure generated with pom.xml_saved will
+not match the current file structure, as noted in the Local Modifcations section. Furthermore, not
+all of the original sources are compatible with the android runtime. Incompatible source files are
+listed in the Android.mk file under "NON_ANDROID_SRC".
+
+Local Modifcations
+-------------------
+The complete original source code was downloaded using pom.xml_saved. The pom file creates a separate
+directory for each jetty package. the src directory of the relevant packages were merged into a single
+src directory. The relevant packages which were merged into the single src directory are:
+-jetty-client
+-jetty-continuation
+-jetty-http
+-jetty-io
+-jetty-security
+-jetty-server
+-jetty-servlet
+-jetty-servlets
+-jetty-util
+-jetty-webapp
+-jetty-websocket
+-jetty-xml
+
+All other packages were discarded. Prebuilt dependencies (SLF4J, the servlet api and jetty utils)
+were copied from the maven repository into the lib directory. pom files and sha1 files are included
+with the prebuilt dependencies for reference.
+
+The eclipse files were created manually. The .classpath file includes the prebuilt dependencies in the
+lib directory and excludes the sources files that are not compatible with the android runtime (see
+"NON_ANDROID_SRC" in the Android.mk file). the .project file was copied from one of the .project
+files created by maven.
+
+Updating this Project
+-------------------
+To update this project, the updated packages should be fetched from maven and their src directories should
+be merged with the local src directory. Any new or updated prebuilt dependencies should be copied into
+the lib directory. The Android.mk file (.classpath file for eclipse) should be updated accordingly. It
+may be necessary to add more files to "NON_ANDROID_SRC" in the Android.mk file (the excluding element
+in .classpath for eclipse).
diff --git a/VERSION.txt b/VERSION.txt
new file mode 100644
index 0000000..c41d18f
--- /dev/null
+++ b/VERSION.txt
@@ -0,0 +1,6209 @@
+jetty-8.1.15.v20140411 - 11 April 2014
+ + 397167 Remote Access documentation is wrong
+ + 419799 complete after exceptions thrown from async error pages
+ + 420776 complete error pages after startAsync
+ + 421197 fix method comment and ensure close synchronized
+ + 422137 Added maxQueued to QueuedThreadPool MBean
+ + 424180 improve bad message errors
+ + 425038 WebSocketClient leaks file handles when exceptions are thrown from
+   open()
+ + 425551 Memory Leak in SelectConnector$ConnectTimeout.expired.
+ + 426658 backport Bug 425930 to jetty-8
+ + 427761 allow endpoints to be interrupted
+ + 428708 JDBCSessionIdManager when clearing expired sessions failed, jetty
+   should still be able to startup
+ + 428710 JDBCSession(Id)Manager use 'read committed isolation level'
+ + 430968 Use wrapped response with async dispatch
+ + 432452 ConnectHandler does not timeout sockets in FIN_WAIT2.
+
+jetty-8.1.14.v20131031 - 31 October 2013
+ + 417772 fixed low resources idle timeout
+ + 418636 Name anonymous filter and holders with classname-hashcode
+ + 419432 Allow to override the SslContextFactory on a per-destination basis.
+ + 420048 DefaultServlet alias checks configured resourceBase
+ + 420530 AbstractLoginModule never fails a login
+
+jetty-7.6.14.v20131031 - 31 October 2013
+ + 417772 fixed low resources idle timeout
+ + 418636 Name anonymous filter and holders with classname-hashcode
+ + 419432 Allow to override the SslContextFactory on a per-destination basis.
+ + 420048 DefaultServlet alias checks configured resourceBase
+ + 420530 AbstractLoginModule never fails a login
+
+jetty-8.1.13.v20130916 - 16 September 2013
+ + 412629 PropertyFileLoginModule doesn't cache user configuration file even
+   for refreshInterval=0
+ + 413484 setAttribute in nosql session management better handles _dirty status
+ + 413684 deprecated unsafe alias checkers
+ + 414235 RequestLogHandler configured on a context fails to handle forwarded
+   requests
+ + 414393 StringIndexOutofBoundsException with > 8k multipart content without
+   CR or LF
+ + 414431 Avoid debug NPE race
+ + 414507 Ensure AnnotationParser ignores parent dir hierarchy when checking
+   for hidden dirnames
+ + 414652 WebSocket's sendMessage() may hang on congested connections.
+ + 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
+ + 415401 Add XmlConfiguration.initializeDefaults that allows to set default
+   values for any XmlConfiguration that may be overridden in the config file
+ + 416266 HttpServletResponse.encodeURL() encodes on first request when only
+   SessionTrackingMode.COOKIE is used
+ + 416585 WebInfConfiguration examines webapp classloader first instead of its
+   parent when looking for container jars
+ + 416787 StringIndexOutOfBounds with a pathMap of ""
+ + 416990 JMX names statically unique
+
+jetty-7.6.13.v20130916 - 16 September 2013
+ + 412629 PropertyFileLoginModule doesn't cache user configuration file even
+   for refreshInterval=0
+ + 413484 setAttribute in nosql session management better handles _dirty status
+ + 413684 deprecated unsafe alias checkers
+ + 414235 RequestLogHandler configured on a context fails to handle forwarded
+   requests
+ + 414393 StringIndexOutofBoundsException with > 8k multipart content without
+   CR or LF
+ + 414431 Avoid debug NPE race
+ + 414507 Ensure AnnotationParser ignores parent dir hierarchy when checking
+   for hidden dirnames
+ + 414652 WebSocket's sendMessage() may hang on congested connections.
+ + 415192 <jsp-file> maps to JspPropertyGroupServlet instead of JspServlet
+ + 415401 Add XmlConfiguration.initializeDefaults that allows to set default
+   values for any XmlConfiguration that may be overridden in the config file
+ + 416585 WebInfConfiguration examines webapp classloader first instead of its
+   parent when looking for container jars
+ + 416990 JMX names statically unique
+
+jetty-8.1.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+   running
+ + 408806 getParameter returns null on Multipart request if called before
+   request.getPart()/getParts()
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409436 NPE on context restart using dynamic servlet registration
+ + 409449 Ensure servlets, filters and listeners added via dynamic
+   registration, annotations or descriptors are cleaned on context restarts
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410405 Avoid NPE for requestDispatcher(../)
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+   server restarts
+ + 410893 async support defaults to false for spec created servlets and filters
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+   the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+   parameters 411459  MultiPartFilter.Wrapper getParameter should use charset
+   encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
+jetty-7.6.12.v20130726 - 26 July 2013
+ + 396706 CGI support parameters
+ + 397193 MongoSessionManager refresh updates last access time
+ + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7
+ + 408529 Etags set in 304 response
+ + 408600 set correct jetty.url in all pom files
+ + 408642 setContentType from addHeader
+ + 408662 In pax-web servlet services requests even if init() has not finished
+   running
+ + 408909 GzipFilter setting of headers when reset and/or not compressed
+ + 409028 Jetty HttpClient does not work with proxy CONNECT method.
+ + 409133 Empty <welcome-file> causes StackOverflowError
+ + 409556 FileInputStream not closed in DirectNIOBuffer
+ + 410630 MongoSessionManager conflicting session update op
+ + 410750 NoSQLSessions: implement session context data persistence across
+   server restarts
+ + 411135 HttpClient may send proxied https requests to the proxy instead of
+   the target server.
+ + 411216 RequestLogHandler handles async completion
+ + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued
+   parameters 411459  MultiPartFilter.Wrapper getParameter should use charset
+   encoding of part
+ + 411755 MultiPartInputStreamParser fails on base64 encoded content
+ + 411909 GzipFilter flushbuffer() results in erroneous finish() call
+ + 412712 HttpClient does not send the terminal chunk after partial writes.
+ + 412750 HttpClient close expired connections fix
+ + 413371 Default JSON.Converters for List and Set.
+ + 413372 JSON Enum uses name rather than toString()
+ + 413684 Trailing slash shows JSP source
+ + 413812 Make RateTracker serializable
+
+jetty-8.1.11.v20130520 - 20 May 2013
+ + 402844 STOP.PORT & STOP.KEY behaviour has changed
+ + 403281 jetty.sh waits for started or failure before returning
+ + 403513 jetty:run goal cannot be executed twice during the maven build
+ + 403570 Asynchronous Request Logging
+ + 404010 fix cast exception in mongodb session manager
+ + 404128 Add Vary headers rather than set them
+ + 404283 org.eclipse.jetty.util.Scanner.scanFile() dies with an NPE if
+   listFiles() returns null
+ + 404325 data constraint redirection does send default port
+ + 404517 Close connection if request received after half close
+ + 404789 Support IPv6 addresses in DoSFilter white list.
+ + 404958 Fixed Resource.newSystemResource striped / handling
+ + 405281 allow filemappedbuffers to not be used
+ + 405537 NPE in rendering JSP using SPDY and wrapped ServletRequest
+ + 406437 Digest Auth supports out of order nc
+ + 406618 Jetty startup in OSGi Equinox fails when using option
+   jetty.home.bundle=org.eclipse.jetty.osgi.boot
+ + 406923 CR line termination
+ + 407136 @PreDestroy called after Servlet.destroy()
+ + 407173 java.lang.IllegalStateException: null when using JDBCSessionManager
+ + 407931 Add toggle for failing on servlet availability
+ + 407976 JDBCSessionIdManager potentially leaves server in bad state after
+   startup
+ + 408077 HashSessionManager leaves file handles open after being stopped
+ + 408446 Multipart parsing issue with boundry and charset in ContentType
+   header
+
+jetty-8.1.10.v20130312 - 12 March 2013
+ + 376273 Early EOF because of SSL Protocol Error on
+   https://api-3t.paypal.com/nvp.
+ + 381521 allow compress methods to be configured
+ + 392129 fixed handling of timeouts after startAsync
+ + 394064 ensure that JarFile instances are closed on JarFileResource.release()
+ + 398649 ServletContextListener.contextDestroyed() is not called on
+   ContextHandler unregistration
+ + 399703 made encoding error handling consistent
+ + 399799 do not hold lock while calling invalidation listeners
+ + 399967 Shutdown hook calls destroy
+ + 400040 NullPointerException in HttpGenerator.prepareBuffers
+ + 400142 ConcurrentModificationException in JDBC SessionManger
+ + 400144 When loading a session fails the JDBCSessionManger produces duplicate
+   session IDs
+ + 400312 ServletContextListener.contextInitialized() is not called when added
+   in ServletContainerInitializer.onStartup
+ + 400457 Thread context classloader hierarchy not searched when finding
+   webapp's java:comp/env
+ + 400859 limit max size of writes from cached content
+ + 401211 Remove requirement for jetty-websocket.jar in WEB-INF/lib
+ + 401317 Make Safari 5.x websocket support minVersion level error more clear
+ + 401382 Prevent parseAvailable from parsing next chunk when previous has not
+   been consumed. Handle no content-type in chunked request.
+ + 401474 Performance problem in org.eclipse.jetty.annotation.AnnotationParser
+ + 401485 zip file closed exception
+ + 401531 StringIndexOutOfBoundsException for "/*" <url-pattern> of
+   <jsp-property-group> fix for multiple mappings to *.jsp
+ + 401908 Enhance DosFilter to allow dynamic configuration of attributes.
+ + 402048 org.eclipse.jetty.server.ShutdownMonitor doesn't stop after the jetty
+   server is stopped
+ + 402485 reseed secure random
+ + 402735 jetty.sh to support status which is == check
+ + 402833 Test harness for global error page and hide exception message from
+   reason string
+
+jetty-7.6.11.v20130520 - 20 May 2013
+ + 402844 STOP.PORT & STOP.KEY behaviour has changed
+ + 403281 jetty.sh waits for started or failure before returning
+ + 403513 jetty:run goal cannot be executed twice during the maven build
+ + 403570 Asynchronous Request Logging
+ + 404010 fix cast exception in mongodb session manager
+ + 404128 Add Vary headers rather than set them
+ + 404283 org.eclipse.jetty.util.Scanner.scanFile() dies with an NPE if
+   listFiles() returns null
+ + 404325 data constraint redirection does send default port
+ + 404517 Close connection if request received after half close
+ + 404789 Support IPv6 addresses in DoSFilter white list.
+ + 404958 Fixed Resource.newSystemResource striped / handling
+ + 405281 allow filemappedbuffers to not be used
+ + 405537 NPE in rendering JSP using SPDY and wrapped ServletRequest
+ + 406437 Digest Auth supports out of order nc
+ + 406923 CR line termination
+ + 407136 @PreDestroy called after Servlet.destroy()
+ + 407173 java.lang.IllegalStateException: null when using JDBCSessionManager
+ + 407976 JDBCSessionIdManager potentially leaves server in bad state after
+   startup
+ + 408077 HashSessionManager leaves file handles open after being stopped
+ + 408446 Multipart parsing issue with boundry and charset in ContentType
+   header
+
+jetty-7.6.10.v20130312 - 12 March 2013
+ + 376273 Early EOF because of SSL Protocol Error on
+   https://api-3t.paypal.com/nvp.
+ + 381521 allow compress methods to be configured
+ + 394064 ensure that JarFile instances are closed on JarFileResource.release()
+ + 398649 ServletContextListener.contextDestroyed() is not called on
+   ContextHandler unregistration
+ + 399703 made encoding error handling consistent
+ + 399799 do not hold lock while calling invalidation listeners
+ + 399967 Shutdown hook calls destroy
+ + 400040 NullPointerException in HttpGenerator.prepareBuffers
+ + 400142 ConcurrentModificationException in JDBC SessionManger
+ + 400144 When loading a session fails the JDBCSessionManger produces duplicate
+   session IDs
+ + 400457 Thread context classloader hierarchy not searched when finding
+   webapp's java:comp/env
+ + 400859 limit max size of writes from cached content
+ + 401211 Remove requirement for jetty-websocket.jar in WEB-INF/lib
+ + 401317 Make Safari 5.x websocket support minVersion level error more clear
+ + 401382 Prevent parseAvailable from parsing next chunk when previous has not
+   been consumed. Handle no content-type in chunked request.
+ + 401474 Performance problem in org.eclipse.jetty.annotation.AnnotationParser
+ + 401531 StringIndexOutOfBoundsException for "/*" <url-pattern> of
+   <jsp-property-group> fix for multiple mappings to *.jsp
+ + 401908 Enhance DosFilter to allow dynamic configuration of attributes.
+ + 402048 org.eclipse.jetty.server.ShutdownMonitor doesn't stop after the jetty
+   server is stopped
+ + 402485 reseed secure random
+ + 402735 jetty.sh to support status which is == check
+ + 402833 Test harness for global error page and hide exception message from
+   reason string
+
+jetty-8.1.9.v20130131 - 31 January 2013
+ + 362226 HttpConnection "wait" call causes thread resource exhaustion
+ + 367638 throw exception for excess form keys
+ + 381521 Only set Vary header when content could be compressed
+ + 382237 support non java JSON classes
+ + 391248 fixing localhost checking in statistics servlet
+ + 391249 fix for invalid XML node dispatchedTimeMean in statistics servlet
+ + 391345 fix missing br tag in statistics servlet
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + 392417 Prevent Cookie parsing interpreting unicode chars
+ + 392492 expect headers only examined for requests>=HTTP/1.1
+ + 393075 1xx 204 and 304 ignore all headers suggesting content
+ + 393158 java.lang.IllegalStateException when sending an empty InputStream
+ + 393220 remove dead code from ServletHandler and log ServletExceptions in
+   warn instead of debug
+ + 393947 additional tests
+ + 393968 fix typo in javadoc
+ + 394294 A web-bundle started before jetty-osgi should be deployed as a webapp
+   when jetty-osgi starts
+ + 394514 Preserve URI parameters in sendRedirect
+ + 394541 remove continuation jar from distro, add as dep to test-jetty-webapp
+ + 394719 remove regex from classpath matching
+ + 394811 Make JAASLoginService log login failures to DEBUG instead of WARN.
+   Same for some other exceptions.
+ + 394829 Session can not be restored after SessionManager.setIdleSavePeriod
+   has saved the session
+ + 394839 Allow multipart mime with no boundary
+ + 394870 Make enablement of remote access to test webapp configurable in
+   override-web.xml
+ + 395215 Multipart mime with just LF and no CRLF
+ + 395380 add ValidUrlRule to jetty-rewrite
+ + 395394 allow logging from boot classloader
+ + 396253 FilterRegistration wrong order
+ + 396459 Log specific message for empty request body for multipart mime
+   requests
+ + 396500 HttpClient Exchange takes forever to complete when less content sent
+   than Content-Length
+ + 396574 add JETTY_HOME as a location for pid to be found
+ + 396886 MultiPartFilter strips bad escaping on filename="..."
+ + 397110 Accept %uXXXX encodings in URIs
+ + 397111 Tolerate empty or excessive whitespace preceeding MultiParts
+ + 397112 Requests with byte-range throws NPE if requested file has no mimetype
+   (eg no file extension)
+ + 397130 maxFormContentSize set in jetty.xml is ignored
+ + 397190 improve ValidUrlRule to iterate on codepoints
+ + 397321 Wrong condition in default start.config for annotations
+ + 397535 Support pluggable alias checking to support symbolic links
+ + 398337 UTF-16 percent encoding in UTF-16 form content
+ + 399132 check parent dir of session store against file to be removed
+ + JETTY-1533 handle URL with no path
+
+jetty-7.6.9.v20130131 - 31 January 2013
+ + 362226 HttpConnection "wait" call causes thread resource exhaustion
+ + 367638 throw exception for excess form keys
+ + 381521 Only set Vary header when content could be compressed
+ + 382237 support non java JSON classes
+ + 391248 fixing localhost checking in statistics servlet
+ + 391249 fix for invalid XML node dispatchedTimeMean in statistics servlet
+ + 391345 fix missing br tag in statistics servlet
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + 392417 Prevent Cookie parsing interpreting unicode chars
+ + 392492 expect headers only examined for requests>=HTTP/1.1
+ + 393075 1xx 204 and 304 ignore all headers suggesting content
+ + 393220 remove dead code from ServletHandler and log ServletExceptions in
+   warn instead of debug
+ + 393947 additional tests
+ + 393968 fix typo in javadoc
+ + 394514 Preserve URI parameters in sendRedirect
+ + 394541 remove continuation jar from distro, add as dep to test-jetty-webapp
+ + 394719 remove regex from classpath matching
+ + 394811 Make JAASLoginService log login failures to DEBUG instead of WARN.
+   Same for some other exceptions.
+ + 394829 Session can not be restored after SessionManager.setIdleSavePeriod
+   has saved the session
+ + 394839 Allow multipart mime with no boundary
+ + 395215 Multipart mime with just LF and no CRLF
+ + 395380 add ValidUrlRule to jetty-rewrite
+ + 395394 allow logging from boot classloader
+ + 396459 Log specific message for empty request body for multipart mime
+   requests
+ + 396500 HttpClient Exchange takes forever to complete when less content sent
+   than Content-Length
+ + 396574 add JETTY_HOME as a location for pid to be found
+ + 396886 MultiPartFilter strips bad escaping on filename="..."
+ + 397110 Accept %uXXXX encodings in URIs
+ + 397111 Tolerate empty or excessive whitespace preceeding MultiParts
+ + 397112 Requests with byte-range throws NPE if requested file has no mimetype
+   (eg no file extension)
+ + 397130 maxFormContentSize set in jetty.xml is ignored
+ + 397190 improve ValidUrlRule to iterate on codepoints
+ + 397321 Wrong condition in default start.config for annotations
+ + 397535 Support pluggable alias checking to support symbolic links
+ + 398337 UTF-16 percent encoding in UTF-16 form content
+ + 399132 check parent dir of session store against file to be removed
+ + JETTY-1533 handle URL with no path
+
+jetty-8.1.8.v20121106 - 06 November 2012
+ + 371170 MongoSessionManager LastAccessTimeTest fails
+ + 388675 Non utf8 encoded query strings not decoded to parameter map using
+   queryEncoding
+ + 388706 Avoid unnecessary indirection through Charset.name
+ + 389390 AnnotationConfiguration is ignored if the metadata-complete attribute
+   is present in an override descriptor regardless of the value
+ + 389452 if web-fragment metadata-complete==true still scan its related jar if
+   there there is a ServletContainerInitializer, ensure webapp restarts work
+ + 389686 Fix reference to org.eclipse.jetty.util.log.stderr.LONG system
+   property in javadoc for StdErrLog
+ + 389956 Bad __context set in WebAppContext.start sequence with respect to ENC
+   setup
+ + 389965 OPTIONS should allow spaces in comma separated list
+ + 390108 Servlet 3.0 API for programmatic login doesn't appear to work
+ + 390161 Apply DeferredAuthentication fix to jaspi
+ + 390163 Implement ServletRegistration.Dynamic.setServletSecurity
+ + 390503 http-method-omission element not being processed
+ + 390560 The method AnnotationParser.getAnnotationHandlers(String) always
+   returns a empty collection.
+ + 391080 Multipart temp files can be left on disk from Request.getPart and
+   getParts
+ + 391082 No exception if multipart input stream incomplete
+ + 391188 Files written with Request.getPart().write(filename) should not be
+   auto-deleted
+ + 391483 fix bad javadoc example in shutdown handler
+ + 391622 Be lenient on RFC6265 restriction on duplicate cookie names in same
+   response
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + 391877 org.eclipse.jetty.webapp.FragmentDescriptor incorrectly reporting
+   duplicate others for after ordering
+ + 392239 Allow no error-code or exception for error-pages
+ + 392525 Add option to --stop-wait to specify timeout
+ + 392641 JDBC Sessions not scavenged if expired during downtime
+ + 392812 MongoSessionIDManager never purges old sessions
+ + 393014 Mongodb purgevalid using query for purgeinvalid
+ + 393015 Mongodb purge not rescheduled
+ + 393075 Jetty WebSocket client cannot connect to Tomcat WebSocket Server
+ + 393218 add xsd=application/xml mime mapping to defaults
+ + 393363 Use Locale.ENGLISH for all toUpperCase and toLowerCase calls
+ + 393368 min websocket version
+ + 393383 delay onClose call until closeOut is done
+ + 393494 HashSessionManager can't delete unrestorable sessions on Windows
+ + JETTY-1547 Jetty does not honor web.xml
+   web-app/jsp-config/jsp-property-group/default-content-type
+
+jetty-7.6.8.v20121106 - 06 November 2012
+ + 371170 MongoSessionManager LastAccessTimeTest fails
+ + 388675 Non utf8 encoded query strings not decoded to parameter map using
+   queryEncoding
+ + 389686 Fix reference to org.eclipse.jetty.util.log.stderr.LONG system
+   property in javadoc for StdErrLog
+ + 389956 Bad __context set in WebAppContext.start sequence with respect to ENC
+   setup
+ + 389965 OPTIONS should allow spaces in comma separated list
+ + 390161 Apply DeferredAuthentication fix to jaspi
+ + 390560 The method AnnotationParser.getAnnotationHandlers(String) always
+   returns a empty collection.
+ + 391483 fix bad javadoc example in shutdown handler
+ + 391622 Be lenient on RFC6265 restriction on duplicate cookie names in same
+   response
+ + 391623 Add option to --stop to wait for target jetty to stop
+ + 392239 Allow no error-code or exception for error-pages
+ + 392525 Add option to --stop-wait to specify timeout
+ + 392641 JDBC Sessions not scavenged if expired during downtime
+ + 392812 MongoSessionIDManager never purges old sessions
+ + 393014 Mongodb purgevalid using query for purgeinvalid
+ + 393015 Mongodb purge not rescheduled
+ + 393075 Jetty WebSocket client cannot connect to Tomcat WebSocket Server
+ + 393218 add xsd=application/xml mime mapping to defaults
+ + 393363 Use Locale.ENGLISH for all toUpperCase and toLowerCase calls
+ + 393368 min websocket version
+ + 393383 delay onClose call until closeOut is done
+ + 393494 HashSessionManager can't delete unrestorable sessions on Windows
+
+jetty-8.1.7.v20120910 - 10 September 2012
+ + 388895 Update dependencies for jetty-jndi
+ + fix busy logging statement re: sessions
+
+jetty-8.1.6.v20120903 - 03 September 2012
+ + 347130 Empty getResourcePaths due to ZipFileClosedException
+ + 367591 Support Env variables in XmlConfiguration.
+ + 377055 Prevent webapp classloader leaks
+ + 379207 backported fixes from jetty-9 to make hierarchy work
+ + 379423 Jetty URL Decoding fails for certain international characters
+ + 383304 Reset PrintWriter on response recycle
+ + 384847 better name
+ + 385049 fix issue with pipelined connections when switching protocols
+ + 385651 Message 'Address already in use' not specific enough
+ + 386010 JspRuntimeContext rewraps System.err
+ + 386591 add UnixCrypt note to about.html
+ + 386714 used deferred auth for form login and error pages
+ + 387896 populate session in SessionAuthentication as a valueBound in addition
+   to activation so it is populate when needed
+ + 387943 Catch CNFE when no jstl jars are installed
+ + 387953 jstl does not work with jetty-7 in osgi
+ + 388072 GZipFilter incorrectly gzips when Accept-Encoding: gzip; q=0
+ + 388073 null session id from cookie causes NPE fixed
+ + 388102 Jetty HttpClient memory leaks when sending larger files
+ + 388393 WebAppProvider doesn't work alongside OSGi deployer
+ + 388502 handle earlyEOF with 500
+ + 388652 Do not flush on handle return if request is suspended
+ + JETTY-1501 Setting custom error response message changes page title
+ + JETTY-1515 Include cookies on 304 responses from DefaultServlet.
+ + JETTY-1527 handle requests with URIs like http://host  (ie no / )
+ + JETTY-1529 Ensure new session that has just been authenticated does not get
+   renewed
+ + JETTY-1532 HTTP headers decoded with platform's default encoding
+ + JETTY-1541 fixed different behaviour for single byte writes
+ + 385925 make SslContextFactory.setProtocols and
+   SslContextFactory.setCipherSuites preserve the order of the given parameters
+
+jetty-8.1.5.v20120716 - 16 June 2012
+ + 376717 Balancer Servlet with round robin support, contribution, added
+   missing license
+ + 379250 Server is added to shutdown hook twice
+ + 380866 maxIdleTime set to 0 after session migration
+ + 381399 Unable to stop a jetty instance that has not finished starting
+ + 381401 Print log warning when stop attempt made with incorrect STOP.KEY
+ + 381402 Make ContextHandler take set of protected directories
+ + 381521 set Vary:Accept-Encoding header for content that might be compressed
+ + 381639 CrossOriginFilter does not support Access-Control-Expose-Headers.
+ + 381712 Support all declared servlets that implement
+   org.apache.jasper.servlet.JspServlet
+ + 381825 leave URI params in forwarded requestURI
+ + 381876 Monitor should wait for child to finish before exiting.
+ + 382343 Jetty XML support for Map is broken.
+ + 383251 500 for SocketExceptions
+ + 383881 WebSocketHandler sets request as handled
+ + 384254 revert change to writable when not dispatched
+ + 384280 Implement preliminary ServletRegistrations
+ + 384847 CrossOriginFilter is not working.
+ + 384896 JDBCSessionManager fails to load existing sessions on oracle when
+   contextPath is /
+ + 384980 Jetty client unable to recover from Time outs when connection count
+   per address hits max.
+ + 385138 add getter for session path and max cookie age that seemed to
+   disappear in a merge long ago
+ + JETTY-1523 It is imposible to map servlet to "/" using
+   WebApplicationInitializer
+ + JETTY-1525 Show handle status in response debug message
+ + JETTY-1530 refine search control on ldap login module
+
+jetty-8.1.4.v20120524 - 24 May 2012
+ + 367608 ignore the aysncrequestreadtest as it is known to fail and is waiting
+   for a fix
+ + 371853 Support bundleentry: protocol for webapp embedded as directory in
+   osgi bundle
+ + 373620 Add ch.qos.logback.access.jetty to the Import-Package for
+   jetty-osgi-boot-logback bundle
+ + 376152 apply context resources recursively
+ + 376801 Make JAAS login modules useable without jetty infrastructure
+ + 377323 Request#getParts() throws ServletException when it should be throwing
+   IllegalStateException
+ + 377391 Manifest updates to jetty-osgi-boot-logback
+ + 377492 NPE if jsp taglibs bundle not deployed
+ + 377550 set charset when content type is set
+ + 377587 ConnectHandler write will block on partial write
+ + 377610 New session not timed out if an old session is invalidated in scope
+   of same request
+ + 377709 Support for RequestParameterCallback missing
+ + 378242 Re-extract war on restart if incomplete extraction
+ + 378273 Remove default Bundle-Localization header
+ + 378487 Null out contextPath on Request.recycle
+ + 379015 Use factored jetty xml config files for defaults
+ + 379046 avoid closing idle connections from selector thread
+ + 379089 DefaultServlet ignores its resourceBase and uses context's
+   ResourceCollection when listing diretories
+ + 379194 ProxyServlet enhancement to enable easy creation of alternative
+   HttpClient implementations
+ + 379909 FormAuthenticator Rembers only the URL of first Request before
+   authentication
+ + 380034 last modified times taken from JarEntry for JarFile resources
+ + 380212 Clear buffer if parsing fails due to full buffer
+ + 380222 JettyPolicyRuntimeTest failure
+
+jetty-8.1.3.v20120416 - 16 April 2012
+ + 349110 MultiPartFilter records the content-type in request params
+ + 367172 Remove detection for slf4j NOPLogger
+ + 372678 Embedded Examples need updates for new LoginService requirement
+ + 373269 Make ServletHandler.notFound() method impl do nothing - override to
+   send back 404.
+ + 373421 address potential race condition related to the nonce queue removing
+   the same nonce twice
+ + 373952 bind called too frequently on refresh
+ + 374018 correctly handle requestperminuted underflow
+ + 374152 jetty-all-server MANIFEST contains wrong import:
+   javax.servlet.annotation;version="[2.6,3)"
+ + 374252 SslConnection.onClose() does not forward to nested connection.
+ + 374258 SPDY leaks SSLEngines. Made the test more reliable.
+ + 374367 NPE in QueuedThreadPool.dump() with early java6 jvms
+ + 374475 Response.sendRedirect does not encode UTF-8 characters properly
+ + 374881 Set copyWebInf to false by default
+ + 374891 enhancement to how ProxyServlet determines the proxy target
+ + 375009 Filter initialization error will throw MultiException
+ + 375083 Flow control should take in account window size changes from
+   concurrent SETTINGS
+ + 375096 If starting a server instance fails in osgi it is cleaned up.
+ + 375490 NPE with --help on command line
+ + 375509 Stalled stream stalls other streams or session control frames. Now
+   using a "death pill" instead of a boolean in order to avoid race conditions
+   where DataInfos were read from the queue (but the boolean not updated yet),
+   and viceversa.
+ + 375594 fixed SSL tests so they are not order dependent
+ + 375709 Ensure resolveTempDirectory failure does not deadlock; improve error
+   message
+ + 375906 Part.getHeader method not case insensitive
+ + 375970 HttpServletRequest.getRemoteAddr() returns null when HTTP is over
+   SPDY.
+ + 376201 HalfClosed state not handled properly. Addendum to restore previous
+   behavior, where a closed stream was also half closed.
+ + 376324 <max-file-size> is not respected in <multipart-config>
+ + JETTY-1495 Ensure dynamic servlet addition does not cause servlets to be
+   inited.
+ + JETTY-1500 form parameters from multipart request not available via
+   request.getParameter
+ + JETTY-1504 HttpServletResponseWrapper ignored when using asyncContext?
+
+jetty-8.1.2.v20120308 - 08 March 2012
+ + 370387 SafariWebsocketDraft0Test failure during build.
+ + 371168 Update ClientCrossContextSessionTest
+ + 372093 handle quotes in Require-Bundle manifest string
+ + 372457 Big response + slow clients + pipelined requests cause Jetty spinning
+   and eventually closing connections. Added a TODO for a method renaming that
+   will happen in the next major release (to avoid break implementers).
+ + 372487 JDBCSessionManager does not work with Oracle
+ + 372806 Command line should accept relative paths for xml config files
+ + 373037 jetty.server.Response.setContentLength(int) should not close a Writer
+   when length=0
+ + 373162 add improved implementation for getParameterMap(), needs a test
+   though and the existing setup doesn't seem like it would easily support the
+   needed test so need to do that still
+ + 373306 Set default user agent extraction pattern for UserAgentFilter
+ + 373567 cert validation issue with ocsp and crldp always being enabled when
+   validating turned on fixed
+ + 373603 NullPointer in WebServletAnnotation
+ + JETTY-1409 GzipFilter will double-compress application/x-gzip content
+ + JETTY-1489 WebAppProvider attempts to deploy .svn folder
+ + JETTY-1494 .
+
+jetty-8.1.1.v20120215 - 15 February 2012
+ + 369121 simplified test
+ + 370120 jvm arguments added via start.ini and --exec are missing spaces
+ + 370137 SslContextFactory does not respect order for
+   [included|excluded]Protocols() and [included|excluded]CipherSuites().
+ + 370368 resolve stack overflow in mongo db session manager
+ + 370386 Remove META-INF from jetty distro
+ + 371040 nosqlsession needs to call correct super contructor for new sessions
+ + 371041 valid was not being set to new mongo db sessions, and the call to
+   mongodb api was wrong in isIdInUse
+ + 371162 NPE protection for nested security handlers
+ + JETTY-1484 Add option for HashSessionManager to delete session files if it
+   can't restore them
+
+jetty-8.1.0.v20120127 - 27 January 2012
+ + 368773 allow authentication to be set by non securityHandler handlers
+ + 368992 avoid update key while flushing during a write
+ + 369216 turned off the shared resource cache
+ + 369349 replace quotes with a space escape method
+
+jetty-8.1.0.RC5 - 20 January 2012
+ + 359329 Prevent reinvocation of LoginModule.login with jaspi for already
+   authed user
+ + 368632 Remove superfluous removal of org.apache.catalina.jsp_file
+ + 368633 fixed configure.dtd resource mappings
+ + 368635 moved lifecycle state reporting from toString to dump
+ + 368773 process data constraints without realm
+ + 368787 always set token view to new header buffers in httpparser
+ + 368821 improved test harness
+ + 368920 JettyAwareLogger always formats the arguments.
+ + 368948 POM for jetty-jndi references unknown version for javax.activation.
+ + 368992 NPE in HttpGenerator.prepareBuffers() test case.
+ + JETTY-1475 made output state fields volatile to provide memory barrier for
+   non dispatched thread IO
+
+jetty-8.1.0.RC4 - 13 January 2012
+ + 365048 jetty Http client does not send proxy authentication when requesting
+   a Https-resource through a web-proxy.
+ + 366774 removed XSS vulnerbility
+ + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
+ + 367433 added tests to investigate
+ + 367435 improved D00 test harness
+ + 367485 HttpExchange canceled before response do not release connection.
+ + 367502 WebSocket connections should be closed when application context is
+   stopped.
+ + 367548 jetty-osgi-boot must not import the nested package twice
+ + 367591 corrected configuration.xml version to 7.6
+ + 367635 Added support for start.d directory
+ + 367716 simplified maxIdleTime logic
+ + 368035 WebSocketClientFactory does not invoke super.doStop().
+ + 368060 do not encode sendRedirect URLs
+ + 368112 NPE on <jsp-config><taglib> element parsing web.xml
+ + 368113 Support servlet mapping to ""
+ + 368114 Protect against non-Strings in System properties for Log
+ + 368189 WebSocketClientFactory should not manage external thread pool. 368240
+   - Improve AggregateLifeCycle handling of shared lifecycles
+ + 368215 Remove debug from jaspi
+ + 368240 Better handling of locally created ThreadPool. Forgot to null out
+   field.
+ + 368291 Change warning to info for NoSuchFieldException on
+   BeanELResolver.properties
+ + 367638 limit number of form parameters to avoid DOS
+ + JETTY-1467 close half closed when idle
+
+jetty-8.1.0.RC2 - 22 December 2011
+ + 359329 jetty-jaspi must exports its packages. jetty-plus must import
+   javax.security
+ + 364638 HttpParser closes if data received while seeking EOF. Tests fixed to
+   cope
+ + 364921 Made test less time sensitive
+ + 364936 use Resource for opening URL streams
+ + 365267 NullPointerException in bad Address
+ + 365375 ResourceHandler should be a HandlerWrapper
+ + 365750 Support WebSocket over SSL, aka wss://
+ + 365932 Produce jetty-websocket aggregate jar for android use
+ + 365947 Set headers for Auth failure and retry in http-spi
+ + 366316 Superfluous printStackTrace on 404
+ + 366342 Dont persist DosFilter trackers in http session
+ + 366730 pass the time idle to onIdleExpire
+ + 367048 test harness for guard on suspended requests
+ + 367175 SSL 100% CPU spin in case of blocked write and RST.
+ + 367219 WebSocketClient.open() fails when URI uses default ports.
+ + 367383 jsp-config element must be returned for
+   ServletContext.getJspConfigDescriptor
+ + JETTY-1460 suppress PrintWriter exceptions
+ + JETTY-1463 websocket D0 parser should return progress even if no fill done
+ + JETTY-1465 NPE in ContextHandler.toString
+
+jetty-8.1.0.RC1 - 06 December 2011
+ + 360245 The version of the javax.servlet packages to import is 2.6 instead of
+   3.0
+ + 365370 ServletHandler can fall through to nested handler
+
+jetty-8.1.0.RC0 - 30 November 2011
+ + 352565 cookie httponly flag ignored
+ + 353285 ServletSecurity annotation ignored
+ + 357163 jetty 8 ought to proxy jetty8 javadocs
+ + 357209 JSP tag listeners not called
+ + 360051 SocketConnectionTest.testServerClosedConnection is excluded.
+ + 361135 Allow session cookies to NEVER be marked as secure, even on HTTPS
+   requests.
+ + 362249 update shell scripts to jetty8
+ + 363878 Add ecj compiler to jetty-8 for jsp
+ + 364283 can't parse the servlet multipart-config for the web.xml
+ + 364430 Support web.xml enabled state for servlets
+
+jetty-7.6.0.RC5 - 20 January 2012
+ + 359329 Prevent reinvocation of LoginModule.login with jaspi for already
+   authed user
+ + 368632 Remove superfluous removal of org.apache.catalina.jsp_file
+ + 368633 fixed configure.dtd resource mappings
+ + 368635 moved lifecycle state reporting from toString to dump
+ + 368773 process data constraints without realm
+ + 368787 always set token view to new header buffers in httpparser
+ + 368821 improved test harness
+ + 368920 JettyAwareLogger always formats the arguments.
+ + 368948 POM for jetty-jndi references unknown version for javax.activation.
+ + 368992 avoid non-blocking flush when writing to avoid setting !_writable
+   without _writeblocked
+ + JETTY-1475 made output state fields volatile to provide memory barrier for
+   non dispatched thread IO
+
+jetty-7.6.0.RC4 - 13 January 2012
+ + 365048 jetty Http client does not send proxy authentication when requesting
+   a Https-resource through a web-proxy.
+ + 366774 removed XSS vulnerbility
+ + 367099 Upgrade jetty-websocket for RFC 6455 - Addendum.
+ + 367716 simplified maxIdleTime logic
+ + 368035 WebSocketClientFactory does not invoke super.doStop().
+ + 368060 do not encode sendRedirect URLs
+ + 368114 Protect against non-Strings in System properties for Log
+ + 368189 WebSocketClientFactory should not manage external thread pool.
+ + 368215 Remove debug from jaspi
+ + 368240 Improve AggregateLifeCycle handling of shared lifecycles
+ + 368291 Change warning to info for NoSuchFieldException on
+   BeanELResolver.properties
+
+jetty-7.6.0.RC3 - 05 January 2012
+ + 367433 added tests to investigate
+ + 367435 improved D00 test harness
+ + 367485 HttpExchange canceled before response do not release connection.
+ + 367502 WebSocket connections should be closed when application context is
+   stopped.
+ + 367591 corrected configuration.xml version to 7.6
+ + 367635 Added support for start.d directory
+ + 367638 limit number of form parameters to avoid DOS
+ + JETTY-1467 close half closed when idle
+
+jetty-7.6.0.RC2 - 22 December 2011
+ + 364638 HttpParser closes if data received while seeking EOF. Tests fixed to
+   cope
+ + 364921 Made test less time sensitive for ssl
+ + 364936 use Resource for opening URL streams
+ + 365267 NullPointerException in bad Address
+ + 365375 ResourceHandler should be a HandlerWrapper
+ + 365750 Support WebSocket over SSL, aka wss://
+ + 365932 Produce jetty-websocket aggregate jar for android use
+ + 365947 Set headers for Auth failure and retry in http-spi
+ + 366316 Superfluous printStackTrace on 404
+ + 366342 Dont persist DosFilter trackers in http session
+ + 366730 pass the time idle to onIdleExpire
+ + 367048 test harness for guard on suspended requests
+ + 367175 SSL 100% CPU spin in case of blocked write and RST.
+ + 367219 WebSocketClient.open() fails when URI uses default ports.
+ + JETTY-1460 suppress PrintWriter exceptions
+ + JETTY-1463 websocket D0 parser should return progress even if no fill done
+ + JETTY-1465 NPE in ContextHandler.toString
+
+jetty-7.6.0.RC1 - 04 December 2011
+ + 352565 cookie httponly flag ignored
+ + 353285 ServletSecurity annotation ignored
+ + 357163 jetty 8 ought to proxy jetty8 javadocs
+ + 357209 JSP tag listeners not called
+ + 360051 SocketConnectionTest.testServerClosedConnection is excluded.
+ + 361135 Allow session cookies to NEVER be marked as secure, even on HTTPS
+   requests.
+ + 362249 update shell scripts to jetty8
+ + 363878 Add ecj compiler to jetty-8 for jsp
+ + 364283 can't parse the servlet multipart-config for the web.xml
+ + 364430 Support web.xml enabled state for servlets
+ + 365370 ServletHandler can fall through to nested handler
+
+jetty-7.6.0.RC0 - 29 November 2011
+ + Refactored NIO layer for better half close handling
+ + 349110 fixed bypass chunk handling
+ + 360546 handle set count exceeding max integer
+ + 362111 StdErrLog.isDebugEnabled() returns true too often
+ + 362113 Improve Test Coverage of org.eclipse.jetty.util.log classes
+ + 362407 setTrustStore(Resource) -> setTrustStoreResource(R)
+ + 362447 add setMaxNonceAge() to DigestAuthenticator
+ + 362468 NPE at line org.eclipse.jetty.io.BufferUtil.putHexInt
+ + 362614 NPE in accepting connection
+ + 362626 IllegalStateException thrown when SslContextFactory preconfigured
+   with SSLContext
+ + 362696 expand virtual host configuration options to ContextHandler and add
+   associated test case for new behavior
+ + 362742 improved UTF8 exception reason
+ + 363124 improved websocket close handling
+ + 363381 Throw IllegalStateException if Request uri is null on getServerName
+ + 363408 GzipFilter should not attempt to compress HTTP status 204
+ + 363488 ShutdownHandler use stopper thread
+ + 363718 Setting java.rmi.server.hostname in jetty-jmx.xml
+ + 363757 partial fix
+ + 363785 StdErrLog must use system-dependent EOL.
+ + 363943 ignore null attribute values
+ + 363993 EOFException parsing HEAD response in HttpTester
+ + 364638 SCEP does idle timestamp checking. New setCheckForIdle method
+   controls onIdleExpired callback. 364921 a second onIdleExpired callback will
+   result in close rather than a shutdown output.
+ + 364657 Support HTTP only cookies from standard API
+ + JETTY-1442 add _hostHeader setter for ProxyRule
+
+jetty-8.0.4.v20111024 - 24 October 2011
+ + 358263 JDBCSessionIdManager add setDatasource(DataSource) method
+ + 358649 Replace existing StdErrLog system properties for DEBUG/IGNORED with
+   LEVEL instead.
+ + 360836 Accept parameters with bad UTF-8. Use replacement character
+ + 360912 CrossOriginFilter does not send Access-Control-Allow-Origin on
+   responses. 355103 Make allowCredentials default to true in
+   CrossOriginFilter.
+ + 360938 Connections closed after a while.
+ + 361135 secure cookies for sessions
+ + 361319 Log initialization does not catch correct exceptions on all jvms
+ + 361325 359292 Allow KeyStore to be set
+ + 361456 release timer task on connection failed
+ + 361655 ExecutorThreadPool.isLowOnThreads() returns wrong value.
+ + JETTY-1444 start threadpool before selector manager
+
+jetty-7.5.4.v20111024 - 24 October 2011
+ + 358263 JDBCSessionIdManager add setDatasource(DataSource) method
+ + 358649 Replace existing StdErrLog system properties for DEBUG/IGNORED with
+   LEVEL instead.
+ + 360836 Accept parameters with bad UTF-8. Use replacement character
+ + 360912 CrossOriginFilter does not send Access-Control-Allow-Origin on
+   responses. 355103 Make allowCredentials default to true in
+   CrossOriginFilter.
+ + 360938 Connections closed after a while.
+ + 361319 Log initialization does not catch correct exceptions on all jvms
+ + 361325 359292 Allow KeyStore to be set
+ + 361456 release timer task on connection failed
+ + 361655 ExecutorThreadPool.isLowOnThreads() returns wrong value.
+ + JETTY-1444 start threadpool before selector manager
+
+jetty-8.0.3.v20111011 - 11 October 2011
+ + 348978 migrate jetty-http-spi
+ + 358649 StdErrLog system properties for package/class logging LEVEL.
+
+jetty-8.0.2.v20111006 - 06 October 2011
+ + 336443 add missing comma in DigestAuthenticator string
+ + 342161 ScannerTest fails intermittently on Mac OS X
+ + 346419 testing HttpClient FDs
+ + 353267 Request._parameters initialization bug
+ + 353509 jetty-client unit tests are running too long
+ + 353627 Basic Auth checks that Basic method has been send
+ + 356144 Allow SelectorManager thread priority to be set
+ + 356274 Start SSL socket factory in call to open()
+ + 357163 jetty 8 ought to proxy jetty8 javadocs
+ + 357178 websockets draft 14 support
+ + 357188 Send content buffer directly
+ + 357209 JSP tag listeners not called
+ + 357216 Logging via Log4J does not expand braces in format strings
+ + 357240 more half close refinements
+ + 357338 remove debug
+ + 357672 resolve issue with serializing pojos with mongodb session manager,
+   thanks to john simone for the discovery and fix
+ + 357959 Include javadoc in distribution
+ + 358027 NullPointerException in ResourceHandler with jetty-stylesheet.css
+ + 358035 idle time only active if > 0
+ + 358147 Add catch for UnknownHostException to fix leaky file descriptor in
+   client
+ + 358164 Dispatch from servlet to handler
+ + 358263 add method for osgi users to register a driver as Class.forName does
+   not work for them
+ + 358649 StdErrLog system properties for package/class logging LEVEL.
+ + 358674 Still allows sslv3 for now
+ + 358687 Updated jsp does not scan for system tlds Fixed pattern.
+ + 358784 JSP broken on Java 1.5
+ + 358925 bit more javadoc on usage
+ + 358959 File descriptor leak with UnresolvedAddressException
+ + 359309 adjust previous test for servletPath to include pathInfo
+ + 359673 updated websocket version handling
+ + 359675 Principal != String, fix for issue in property file login manager
+ + 360051 SocketConnectionTest.testServerClosedConnection is excluded.
+ + 360066 jsps referenced in web.xml <jsp-file> elements do not compile
+ + JETTY-1130 Access Sessions from HashSessionIdManager
+ + JETTY-1277 Fixed sendRedirect encoding of relative locations
+ + JETTY-1322 idle sweeper checks for closed endp
+ + JETTY-1377 extra logging for busy selector
+ + JETTY-1378 new sys property for the latest jsp-impl to force the use of the
+   JDTCompiler when running in OSGi.
+ + JETTY-1414 applied to PropertyUserStore
+ + JETTY-1415 Start/Stop Server and Client only once in test, code format
+ + JETTY-1420 Set Host header for new request in RedirectListener
+ + JETTY-1421 Implement RedirectListener.onException,onConnectionFailed
+ + JETTY-1423 force connection to be closed returned
+ + JETTY-1430 local JNDI contexts don't carry environment
+ + JETTY-1434 Add a jsp that exercises jstl.
+ + JETTY-1439 space in directory installation path causes classloader problem
+
+jetty-7.5.3.v20111011 - 11 October 2011
+ + 348978 migrate jetty-http-spi
+ + 358649 StdErrLog system properties for package/class logging LEVEL.
+
+jetty-7.5.2.v20111006 - 06 October 2011
+ + 336443 check nonce count is increasing
+ + 342161 ScannerTest fails intermittently on Mac OS X
+ + 346419 testing HttpClient FDs
+ + 353267 Request._parameters initialization bug
+ + 353509 jetty-client unit tests are running too long
+ + 353627 Basic Auth checks that Basic method has been send
+ + 356144 Allow SelectorManager thread priority to be set
+ + 356274 Start SSL socket factory in call to open()
+ + 357178 websockets draft 14 support
+ + 357188 Send content buffer directly
+ + 357209 JSP tag listeners not called
+ + 357216 Logging via Log4J does not expand braces in format strings
+ + 357240 more half close refinements
+ + 357338 remove debug
+ + 357672 resolve issue with serializing pojos with mongodb session manager,
+   thanks to john simone for the discovery and fix
+ + 357959 Include javadoc in distribution
+ + 358027 NullPointerException in ResourceHandler with jetty-stylesheet.css
+ + 358035 idle time only active if > 0
+ + 358147 Add catch for UnknownHostException to fix leaky file descriptor in
+   client
+ + 358164 Dispatch from servlet to handler
+ + 358263 add method for osgi users to register a driver as Class.forName does
+   not work for them
+ + 358649 StdErrLog system properties for package/class logging LEVEL.
+ + 358674 Still allows sslv3 for now
+ + 358687 Updated jsp does not scan for system tlds Fixed pattern.
+ + 358784 JSP broken on Java 1.5
+ + 358925 bit more javadoc on usage
+ + 358959 File descriptor leak with UnresolvedAddressException
+ + 359309 adjust previous test for servletPath to include pathInfo
+ + 359673 updated websocket version handling
+ + 359675 Principal != String, fix for issue in property file login manager
+ + 360051 SocketConnectionTest.testServerClosedConnection is excluded.
+ + 360066 jsps referenced in web.xml <jsp-file> elements do not compile
+ + JETTY-1130 Access Sessions from HashSessionIdManager
+ + JETTY-1277 Fixed sendRedirect encoding of relative locations
+ + JETTY-1322 idle sweeper checks for closed endp
+ + JETTY-1377 extra logging for busy selector
+ + JETTY-1378 new sys property for the latest jsp-impl to force the use of the
+   JDTCompiler when running in OSGi.
+ + JETTY-1414 applied to PropertyUserStore
+ + JETTY-1415 Start/Stop Server and Client only once in test, code format
+ + JETTY-1420 Set Host header for new request in RedirectListener
+ + JETTY-1421 Implement RedirectListener.onException,onConnectionFailed
+ + JETTY-1423 force connection to be closed returned
+ + JETTY-1430 local JNDI contexts don't carry environment
+ + JETTY-1434 Add a jsp that exercises jstl.
+ + JETTY-1439 space in directory installation path causes classloader problem
+
+jetty-8.0.1.v20110908 - 08 September 2011
+ + 350634 Added Resource.newResource(File)
+ + 356190 fix monodb tests  for changed test api
+ + 356428 removed timed waits from test
+ + 356693 reduce visibility to webapp of websocket implementations
+ + 356695 jetty server jars are provided for websockets
+ + 356726 Instead of the sessionDestroyed called sessionCreated after
+   invalidate session
+ + 356751 Add null protection to ServletContextHandler.doStop
+ + 356823 correctly decode close codes.  Send not utf-8 close code.
+ + 357058 Acceptor thread blocking
+
+jetty-7.5.1.v20110908 - 08 September 2011
+ + 350634 Added Resource.newResource(File)
+ + 356190 fix monodb tests  for changed test api
+ + 356428 removed timed waits from test
+ + 356693 reduce visibility to webapp of websocket implementations
+ + 356695 jetty server jars are provided for websockets
+ + 356726 Instead of the sessionDestroyed called sessionCreated after
+   invalidate session
+ + 356751 Add null protection to ServletContextHandler.doStop
+ + 356823 correctly decode close codes.  Send not utf-8 close code.
+ + 357058 Acceptor thread blocking
+
+jetty-8.0.0.v20110901 - 01 September 2011
+ + 352565 cookie httponly flag ignored
+ + 353073 better warnings
+ + 353285 ServletSecurity annotation ignored
+ + 356421 Upgraded websocket to draft 13 support
+
+jetty-7.5.0.v20110901 - 01 September 2011
+ + 356421 Upgraded websocket to draft 13 support
+ + 353073 better warnings
+
+jetty-7.5.0.RC2 - 30 August 2011
+ + 293739 Hide stacks in named log testing. Various other minor log cleanups in
+   output.
+ + 352188 TestClient correctly processes --host option in jetty-websocket
+ + 352222 Moved JmxMonitor functionality from Codehaus
+ + 353014 TimeoutExchangeTest run time reduced
+ + 353073 deprecated non factory method for websocket clients
+ + 353192 Better warning for classes of wrong type
+ + 353623 Added new methods to HttpExchange
+ + 353624 HttpURI accepts java.net.URI object in constructor
+ + 354080 ServletContextHandler allows to replace any subordinate handler when
+   restarted
+ + 355478 set public to HashedSession, looks like honest mistake and not by
+   design to be this way
+ + 355854 remove automatic conversion in favor of issuing a warning for
+   jetty-web.xml that can't be processed
+ + 356128 Moved integration tests from jetty-monitor to test-integration module
+ + 356137 Upgrade to jsp implementation version 2.1.3-b10
+ + 356144 added SelectorManager.setSelectorPriorityDelta(int)
+ + JETTY-1410 handle 1xx in similar fashion to 401s and 302s
+
+jetty-7.5.0.RC1 - 19 August 2011
+ + 276670 SLF4J loggers show correct location information
+ + 335001 Eliminate expected exceptions from log when running in JBoss
+ + 355103 Make allowCredentials default to true in CrossOriginFilter
+ + 355162 Allow creating an empty resource collection
+ + JETTY-1410 HTTP client handles CONTINUE 100 response correctly
+ + JETTY-1414 HashLoginService doesn't refresh realm if specified config
+   filename is not an absolute platform specific value
+
+jetty-8.0.0.RC0 - 16 August 2011
+ + Merge from jetty-7.4.3
+ + Enable annotations by default
+ + 352565 cookie httponly flag ignored
+ + 353285 ServletSecurity annotation ignored
+
+jetty-8.0.0.M3 - 27 May 2011
+ + 324505 Implement API login
+ + 335500 request.getParts() throws a NullPointerException
+ + 343472 isUserInRole does not prevent subsequent login call.
+ + 346180 jsp-2.2 support
+ + Updated to jetty-7.4.2.v20110526
+
+jetty-7.5.0.RC0 - 15 August 2011
+ + 298502 Handle 200 Connect responses with no content-length
+ + 347484 / - > ${/} in some paths in grant codebases
+ + 349005 add javadoc detailing the convenience hack of removing leading /'s
+ + 351516 Refactored sessions to better support nosql session managers
+ + 351576 Do not use deprecated method File.toURL()
+ + 352046 Need try/catch around features set in XmlParser
+ + 352133 Generally resolve java 1.5isms
+ + 352176 xml parsing on startElement should be more flexible on using qName or
+   localName
+ + 352421 HttpURI paths beginning with '.'
+ + 352684 Implemented spinning thread analyzer
+ + 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
+ + 352999 ExpireTest running too long
+ + 353073 WebSocketClient
+ + 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
+ + 353165 addJars can follow symbolic link jar files
+ + 353210 Bundle-Version in o.e.j.o.boot.logback fix
+ + 353465 JAASLoginService ignores callbackHandlerClass
+ + 353563 HttpDestinationQueueTest too slow
+ + 353862 Improve performance of QuotedStringTokenizer.quote()
+ + 354014 Content-Length is passed to wrapped response in GZipFilter
+ + 354204 Charset encodings property file not used
+ + 354397 RewriteRegexRule handles special characters in regex group
+ + 354466 Typo in example config of jetty-plus.xml
+
+jetty-7.4.5.v20110725 - 25 July 2011
+ + 347484 / - > ${/} in some paths in grant codebases
+ + 352133 resolve some 1.5isms
+ + 352421 HttpURI paths beginning with '.'
+ + 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
+
+jetty-7.4.4.v20110707 - 07 July 2011
+ + 308851 Converted all jetty-client module tests to JUnit 4
+ + 345268 JDBCSessionManager does not work with maxInactiveInterval = -1
+ + 350397 SelectChannelConnector does not shutdown gracefully
+ + 350634 Reverted FileResource constructor changes
+ + 351039 Forward dispatch should retain locale
+ + 351199 HttpServletResponse.encodeURL() wrongly encodes an url without path
+   when cookies are disabled
+ + JETTY-1153 Default charset/encoding of HTTP POST requests
+ + JETTY-1380 Jetty Rewrite example does not work in Hightide
+
+jetty-7.4.3.v20110701 - 01 July 2011
+ + 295832 ProxyServlet more extensible and configurable
+ + 302566 GZIP handler for embedded Jetty servers
+ + 308851 Converted HttpExchangeTest and related tests to JUnit 4
+ + 324704 JDBC Session Manager reloading session
+ + 332200 Eliminate expected exceptions from log while using
+   org.eclipse.jetty.jmx bundle
+ + 347468 o.e.j.deploy.binding.GlobalWebappConfigBindingTest fails on Windows
+   platform
+ + 347617 Dynamically install/update/remove OSGi bundles discovered in the
+   contexts folder
+ + 347717 start.jar destroys dependent child of --exec
+ + 347889 OSGi should follow directive visibility:=reexport for
+   META-INF/web-fragments and resources
+ + 347898 Close channel on JVM exceptions
+ + 348652 jetty.sh starts two unix processes
+ + 348935 Close A tag in directory listing
+ + 349344 Passing empty query string to UrlEncoded#decodeTo(String, MultiMap,
+   String) does not yield an empty map
+ + 349738 set buffer sizes for http client in proxy servlet
+ + 349870 proxy servlet protect continuation against fast failing exchanges
+ + 349896 SCEP supports zero maxIdleTime
+ + 349897 draft -09 websockets
+ + 349997 MBeanContainer uses weak references
+ + 350533 Add "Origin" to the list of allowed headers in CrossOriginFilter
+ + 350634 Cleanup FileResource construction
+ + 350642 Don't close SCEP during NIOBuffer manipulation
+ + JETTY-1342 Recreate selector in change task
+ + JETTY-1385 NPE in jetty client's
+   HTttpExchange.setRequestContentSource(InputStream)
+ + JETTY-1390 RewriteHandler handles encoded URIs
+
+jetty-7.4.2.v20110526
+ + 334443 Improve the ability to specify extra class paths using the Jetty
+   Maven Plugin
+ + 336220 tmp directory is not set if you reload a webapp with
+   jetty-maven-plugin
+ + 338364 Fixed expires header for set cookies
+ + 345729 binding for managing server and system classes globally
+ + 345615 Enable SSL Session caching
+ + 345763 Source file is updated during the build
+ + 345873 Update jetty-ssl.xml to new style
+ + 345900 Handle IPv6 with default port
+ + 346014 Fixed full HttpGenerator
+ + 346124 ServletContext resources paths not resolved correctly when using UNC
+   shares
+ + 346179 o.e.j.util.ScannerTest fails on MacOS X platform
+ + 346181 o.e.j.server.StressTest stalls on MacOS X platform
+ + 346998 AbstractLifeCycle.isRunning() returns false if state changes from
+   STARTING to STARTED during call
+ + 346614 HttpConnection.handle() spins in case of SSL truncation attacks
+ + 346764 OrderedGroupBinding deployment binding
+ + 347137 Allow SSL renegotiations by default in HttpClient
+ + 374174 Consistent mbean names
+ + JETTY-1146 Encode jsessionid in sendRedirect
+ + JETTY-1342 Recreate selector if wakeup throws JVM bug
+
+jetty-7.4.1.v20110513
+ + 288563 remove unsupported and deprecated --secure option
+ + 332907 Add context property to ObjectName of JMX MBeans
+ + 336056 Ability to override the computation of the ContextHandler to deploy
+   the DefaultServlet on the HttpService
+ + 340040 Support for a total timeout
+ + 343083 Set nested dispatch type and connection
+ + 343172 Check package implementor for version
+ + 343277 add support for a context white list
+ + 343352 make sure that jetty.osgi.boot is activated when a WAB is registered
+ + 343482 refactored overlay deployer layout to use WAR layout
+ + 343567 HttpClient does not limit the destination's exchange queue
+ + 343680 Handle OSGi bundle jars not ending in ".war"
+ + 343707 'REQUEST' is printed on console for each incoming HTTP request
+ + 343923 flush timeouts applied to outer loop
+ + 343936 Session idle calls unbind and remove listeners
+ + 344059 Websockets draft-07
+ + 344067 Add support for OSGi fragment bundles to add static resources to
+   web-bundles
+ + 344513 Attempting to set ConfigurationClasses in jetty-web.xml causes NPE
+ + 344529 Ability to customize the error handling of the OSGi HttpService
+ + 345047 Readded deprecated ScanningAppDeployer#setMonitoredDir
+ + 345290 Weak references from SessionIdManager. HashSessionManager cleanup.
+ + 345543 Always close endpoint on SSLException
+ + 345656 Disambiguate SslContextFactory#validateCerts property
+ + 345679 Allow setting an initialized KeyStore as keystore/truststore of
+   SslContextFactory
+ + 345704 jetty-nested works with forwarded SSL in cloudfoundry
+ + JETTY-954 WebAppContext eats any start exceptions instead of stopping the
+   server load
+ + JETTY-1314 Handle bad URI encodings
+ + JETTY-1324 Tested not using CESU-8 instead of UTF-8
+ + JETTY-1326 Invoker names not hashCode based
+ + JETTY-1343 IllegalArgumentException for bad % encodings
+ + JETTY-1347 Updated ServletHander javadoc
+
+jetty-7.4.0.v20110414
+ + 342504 Scanner Listener
+ + 342700 refine websocket API for anticipated changes
+ + JETTY-1362 Set root cause of UnavailableException
+ + Various test harness cleanups to avoid random failures
+
+jetty-7.4.0.RC0
+ + 324110 Added test harnesses for merging of QueryStrings.
+ + 337685 Update websocket API in preparation for draft -07
+ + 338627 HashSessionManager.getIdleSavePeriod returns milliseconds instead of
+   seconds
+ + 338807 Ignore content length in 1xx, 204, 304 responses
+ + 338819 Externally control Deployment Manager application lifecycle
+ + 339084 Fixed NPE with servlet 3.0 async listener
+ + 339150 Validate client certificate when it is used for authentication
+ + 339187 In the OSGi manifest of the jetty-all-server aggregate, mark
+   javax.annotation as optional
+ + 339543 Add configuration options for Certificate Revocation checking
+ + 340265 Improve handling of io shutdown in SSL
+ + 340621 Added SizedThreadPool interface
+ + 340636 HashSessionManager lazy loads all sessions
+ + 340838 Update ConnectHandler to perform half closes properly
+ + 340878 Integrations should be able to load their own keystores
+ + 340920 Dynamically assign RMI registry port for integration testing
+ + 340949 Scanner delays file notifications until files are stable
+ + 341006 Move inner enums out into separate file
+ + 341105 Stack trace is printed for an ignored exception
+ + 341145 WebAppContext MBean attribute serverClasses returns empty value
+ + 341171 Locking in HttpDestination blocks all requests to the same address
+ + 341206 Stop order is wrong in HandlerWrapper
+ + 341255 org.eclipse.http usage in AJP/SessionId linkage
+ + 341386 Remote close not detected by HttpClient
+ + 341394 Remove 'Unavailable' JMX attributes of WebAppContext MBean
+ + 341439 Blocking HttpClient does not use soTimeout for timeouts
+ + 341561 Exception when adding o.e.j.s.DoSFilter as managed attribute
+ + 341692 Fixed deadlock if stopped while starting
+ + 341736 Split jetty-nested out of war module
+ + 341726 JSONPojoConverter handles characters
+ + 341992 Overlayed context deployer
+ + 341694 Disable AJP buffer resizing
+ + 341850 Protect QTP dump from bad stacks
+ + JETTY-1245 Pooled Buffers implementation
+ + JETTY-1354 Added jetty-nested
+ + Ensure generated fragment names are unique
+ + Added extra session removal test
+
+jetty-8.0.0.M2 - 16 November 2010
+ + 320073 Reconsile configuration mechanism
+ + 321068 JSF2 fails to initialize
+ + 324493 Registration init parameter handling null check, setInitParameters
+   additive
+ + 324505 Request.login method must throw ServletException if it cant login
+ + 324872 allow disabling listener restriction from using *Registration
+   interfaces
+ + 327416 Change meaning of @HandlesTypes in line with latest interpretation by
+   JSR315
+ + 327489 Change meaning of @MultipartConfig to match servlet spec 3.0
+   maintenance release 3.0a
+ + 328008 Handle update to Servlet Spec 3 Section 8.2.3.h.ii
+ + 330188 Reject web-fragment.xml with same <name> as another already loaded
+   one
+ + 330208 Support new wording on servlet-mapping and filter-mapping merging
+   from servlet3.0a
+ + 330292 request.getParts() returns only one part when the name is the same
+ + Update to jetty-7.2.1.v20101111
+
+jetty-7.3.1.v20110307 - 07 March 2011
+ + 316382 Support a more strict SSL option with certificates
+ + 333481 Handle UCS-4 codepoints in decode and encode
+ + 335329 Moved blocking timeout handling to outside try catch
+ + 336668 policy supports cert validation
+ + 336691 Possible wrong length returned by ChannelEndPoint.flush() in case of
+   RandomAccessFileBuffer
+ + 336781 If xml parser is not validating, turn off external dtd resolution
+ + 336793 Tee data filled and flushed from endpoint
+ + 337258 Scanner start and end cycle notification
+ + 337268 Allow specifying alias of a certificate to be used by SSL connector
+ + 337270 Shared Timer for session management
+ + 337271 Flush SSL endpoint when dispatch thread held forever
+ + 337678 Readded optional async connection mode for HttpClient
+ + 337685 Work in progress on draft 6 websockets
+ + 337746 Fixed Session deIdle recursion
+ + 337784 Improve HashSessionManager for session migrations
+ + 337878 Extra tests of security constraints
+ + 337896 HttpExchange.timeout does not override HttpClient.timeout
+ + 337898 set client HttpConnection max idle time from exchange timeout
+ + 338035 Default acceptors 0.25*CPUs and improved selector/acceptor thread
+   names.
+ + 338068 Leaking ConstraintMappings on redeploy
+ + 338092 ProxyServlet leaks memory
+ + 338607 Removed managed attributes when context is stopped
+ + 338819 Externally control Deployment Manager application lifecycle
+ + JETTY-1304 Allow quoted boundaries in Multipart filter
+ + JETTY-1317 More elegent handling of bad URIs in requests
+ + JETTY-1331 Allow alternate XML configuration processors (eg spring)
+ + JETTY-1335 HttpClient's SelectConnector clean-up
+ + JETTY-1333 HttpClient _timeout and _soTimeout is messed up
+ + JETTY-1337 Workname cannot contain '.'
+ + JETTY-1338 Trust default SecureRandom seed
+
+jetty-7.3.0.v20110203 - 03 February 2011
+ + JETTY-1259 NullPointerException in JDBCSessionIdManager when invalidating
+   session (further update)
+ + 296978 standardizing various Testing Util Classes to jetty-test-helper
+ + 319178 test failure fix in jetty-util on windows
+ + 320457 add SPNEGO support
+ + 324505 Implement API login
+ + 328872 Multi Jetty xml files not loading if directory is referenced in
+   jetty.conf
+ + 329746 client option to set just truststore and use strict ssl context
+ + 331803 Update XML configuration files to use proper arguments for startup
+   command in examples
+ + 332179 Fixed formatting of negative dates
+ + 332432 Scanner.java now always scanning the canonical form of File
+ + 332517 Improved DefaultServlet debug
+ + 332703 Cleanup context scope JNDI at stop
+ + 332796 Annotations inheritance does not work with jetty7
+ + 332799 100% CPU on redeploy session invalidation
+ + 332937 Added Destroyable Dumpable interfaces and reworked dependent
+   lifecycles, specially of JNDI
+ + 333247 fix api compat issue in ConstraintSecurityHandler
+ + 333415 wired up HttpInput.available and added test harnesses
+ + 333481 Handle UTF-32 codepoints in decode and encode
+ + 333608 tlds defined in web.xml are not picked up
+ + 333679 Refactored jetty-jmx. Moved mbeans to modules
+ + 333717 HttpExchange able to return local address used
+ + 333771 System properties are not available inside XML configuration file by
+   using the 'property' tag
+ + 333875 Monitor public constructor
+ + 333892 Improved JVM bug detection
+ + 334062 It should be possible to embed in the jetty.home.bundle the ssl
+   keystore files
+ + 334229 javax-security needs to import the package javax.security.cert in its
+   OSGi manifest
+ + 334311 fix buffer reuse issue in CachedExchange
+ + 335329 Stop SSL spin during handshake and renogotiate
+ + 335361 Fixed 'jetty.sh check' to show current PID when JETTY_PID env.
+   variable is set
+ + 335641 Cleaned up dispatch handling to avoid key.interestOps==0 when
+   undispatched
+ + 335681 Improve ChannelEndPoint.close() to avoid spinning
+ + 335836 Race when updating SelectChannelEndPoint._dispatched
+
+jetty-7.2.2.v20101205 - 05 December 2010
+ + JETTY-1308 327109 (re)fixed AJP handling of empty packets
+ + 331703 Fixed failing OSGI test TestJettyOSGiBootWithJsp.java on MacOSX
+ + 331567 IPAccessHandlerTest failed on MacOS fix
+ + 328789 Clean up tmp files from test harnesses
+ + 331230 Fixed low thread warnings when acceptors>threadpool
+ + 331461 Fixed idle timeout for unflushed HTTP/1.0
+ + JETTY-1307 Check that JarFileResource directories end with /
+ + 330210 Improve performance of writing large bytes arrays
+ + 330208 Support new wording on servlet-mapping and filter-mapping merging
+   from servlet3.0a
+ + 330188 Reject web-fragment.xml with same <name> as another already loaded
+   one
+ + 330229 Jetty tries to parse META-INF/*.tld when jsp-api is not on classpath,
+   causing DTD entity resoluton to fail
+ + 330265 start.jar --stop kills --exec subprocess
+ + 330417 Atomic PUT in PutFilter
+ + 330419 Reloading webapp duplicates StandardDescriptorProcessor
+ + 330686 OSGi: Make org.eclipse.jetty.jsp-2.1 a fragment of
+   org.apache.jasper.glassfish
+ + 330732 Removed System.err debugging
+ + 330764 Command line properties passed to start.jar --exec
+ + JETTY-1297 Improved matching of vhosts so that a vhost match has priority
+
+jetty-7.2.1.v20101111 - 11 November 2010
+ + 324679 Fixed dedection of write before static content
+ + 328199 Ensure blocking connectors always close socket
+ + 328205 Improved SelectManager stopping
+ + 328306 Serialization of FormAuthentication
+ + 328332 Response.getContentType works with setHeader
+ + 328523 Fixed overloaded setters in AppProvider
+ + 328008 Handle update to Servlet Spec 3 Section 8.2.3.h.ii
+ + 328778 Improved javadoc for secure session cookies
+ + 328782 allow per connection max idle time to be set
+ + 328885 web overrides do not override
+ + 328988 Idle saving of session values
+ + 329180 Spin check for Selector to stop
+ + 329410 Enforce XmlConfiguration properties as Map<String,String>
+ + 329602 only clear ServletContext attributes on doStop
+ + 329642 Concurrent modification exception in Deployment Manager
+ + 329643 Improved deployment of resource collections
+ + JETTY-748 Prevent race close of socket by old acceptor threads
+ + JETTY-1291 Extract query parameters even if POST content consumed
+ + JETTY-1295 Contexts mixed up when hot-deploying on virtual hosts
+ + JETTY-1297 Make ServletContext.getContext(String) virtual host aware
+
+jetty-6.1.26 - 10 November 2010
+ + JETTY-748 Prevent race close of socket by old acceptor threads
+ + JETTY-1239 HTAccessHandler [allow from 127.0.0.1] does not work
+ + JETTY-1291 Extract query parameters even if POST content consumed
+ + JETTY-1293 Avoid usage of String.split
+ + JETTY-1296 Always clear changes list in selectManager
+
+jetty-6.1.26.RC0 - 20 October 2010
+ + JETTY-547 Improved usage of shutdownOutput before close.
+ + JETTY-912 add per exchange timeout
+ + JETTY-1051 offer jetty.skip flag for maven plugin
+ + JETTY-1096 exclude maven and plexus classes from jetty plugin
+ + JETTY-1248 Infinite loop creating temp MultiPart files
+ + JETTY-1264 Idle timer deadlock
+ + JETTY-1271 Handle unavailable request
+ + JETTY-1278 J2se6 SPI filter handling fix
+ + JETTY-1283 Allow JSONPojoConvertorFactory to set fromJSON
+ + JETTY-1287 rewrite handler thread safe issue resolved
+ + JETTY-1288 info when atypical classloader set to WebAppContext
+ + JETTY-1289 MRU cache for filter chains
+ + JETTY-1292 close input streams after keystore.load()
+ + 325468 Clean work webapp dir before unpack
+ + 327109 Fixed AJP handling of empty packets
+ + 327562 Implement all X-Forwarded headers in ProxyServlet
+
+jetty-7.2.0.v20101020 - 20 October 2010
+ + 289540 added javadoc into distribution
+ + 297154 add source distribution artifact
+ + 323985 Xmlconfiguration pulls start.jar config properties
+ + 324369 Improved handling of multiple versions of
+   draft-ietf-hybi-thewebsocketprotocol
+ + 326734 Configure Digest maxNonceAge with Security handler init param
+ + 327109 Fixed AJP handling of empty packets
+ + 327183 Allow better configurability of HttpClient for TLS/SSL
+ + 327469 removed needless java6 dependencies
+ + 327562 Implement all X-Forwarded headers in ProxyServlet
+ + 327601 Multipart Filter handles quoted tokens
+ + 327725 Nested ResourceCaches
+ + 328199 Ensure blocking connectors always close socket
+ + 328205 Improved SelectManager stopping
+ + 328273 Added serializable to default user identity
+ + JETTY-1288 Info statement when atypical classloader set on WebAppContext
+ + JETTY-1289 LRU cache for filter chains
+
+jetty-7.2.0.RC0 - 01 October 2010
+ + 314087 Simplified SelectorManager
+ + 319334 Concurrent, sharable ResourceCache
+ + 319370 WebAppClassLoader.Context
+ + 319444 Two nulls are appended to log statements from ContextHanler$Context
+ + 320073 Reconsile configuration mechanism
+ + 320112 Websocket in aggregate jars
+ + 320264 Removed duplicate mime.property entries
+ + 320457 Added rfc2045 support to B64Code
+ + 321232 BasicAuthenticator ignores bad Authorization header.
+ + 321307 HashSessionManager calls passivation listeners.
+ + 321730 SelectChannelEndPoint prints to System.err
+ + 321735 HttpClient onException called for buffer overflow.
+ + 322448 Added jetty-dir.css for directory listings
+ + 322575 NPE in HotSwapHandler if old handler null
+ + 322683 RewriteHandler thread safety
+ + 323196 org.mortbay properties to org.eclipse
+ + 323435 MovedContextHandler permanent redirection
+ + 323464 IPv6 localhost with no Host header
+ + 324110 Merge async dispatch parameters
+ + 324158 Durable download or Orbit jars
+ + 324260 Jetty-6 continuations handle complete calls
+ + 324359 illegal actions on AsyncContext should not change its state.
+ + 324360 validate input on getResource since loop logic obscures subclass
+   input validation.
+ + 324369 Implement draft-ietf-hybi-thewebsocketprotocol-01
+ + 324377 Allow dispatch of ServletRequest and ServletResponse
+ + 324379 Change content type after getWriter
+ + 324501 Fire RequestListener.requestDestroyed in last-to-first order.
+ + 324601 Check session expiry on access
+ + 324679 Allow filter to write before static content
+ + 324811 NPE in Server.dump
+ + 324812 restore WebAppContext constructor used by geronimo integration
+ + 325072 include to DefaultServlet of missing file throws
+   FileNotFoundException
+ + 325105 websocket ondisconnect fixed
+ + 325128 websocket send during onConnect
+ + 325468 Clean work webapp dir before unpack
+ + 326612 Handle X-Forwarded-Proto header
+ + JETTY-912 added per exchange timeout api
+ + JETTY-1063 Plugin problems with spaces in classpath resource references
+ + JETTY-1245 Do not use direct buffers with NIO SSL
+ + JETTY-1249 Apply max idle time to all connectors
+ + JETTY-1250 Parallel start of HandlerCollection
+ + JETTY-1256 annotation and jta jars from Orbit
+ + JETTY-1259 NullPointerException in JDBCSessionIdManager when invalidating
+   session
+ + JETTY-1261 errant listener usage in StandardDescriptorProcessor
+ + JETTY-1263 JDBCSessionIdManager table creation fails on Oracle
+ + JETTY-1265 Reason field option in client response
+ + JETTY-1266 Destroy sessions before filters/servlets
+ + JETTY-1268 Form Auth saves POST data
+ + JETTY-1269 Improve log multithreadedness
+ + JETTY-1270 Websocket closed endp protection
+ + JETTY-1271 handled unavailable exception
+ + JETTY-1279 Make jetty-plus.xml enable plus features for all webapps by
+   default
+ + JETTY-1281 Create new session after authentication
+ + JETTY-1283 JSONPojoConvertorFactory can turn off fromJSON
+ + Fix jetty-plus.xml for new configuration names
+ + Added ignore to Logger interface
+ + Improved debug dump
+
+jetty-7.1.6.v20100715
+ + 319519 Warn about duplicate configuration files
+ + 319655 Reset HEAD status
+ + JETTY-1247 synchronize recylcing of SSL NIO buffers
+ + JETTY-1248 fix parsing of bad multiparts
+ + JETTY-1249 Apply max idle time to all connectors
+ + JETTY-1251 Replace then close selector for JVM bugs
+
+jetty-8.0.0.M1 - 12 July 2010
+ + 306350 Ensure jars excluded by ordering are not scanned for annotations
+ + JETTY-1224 Change jetty-8 merge rules for fragment descriptors and
+   annotations
+ + Ensure <absolute-ordering> in web.xml overrides relative <ordering> in
+   fragments
+ + Ensure empty <absolute-ordering> implies exclusion of all fragments
+ + Ensure servlet-api jar class inheritance hierarchy is scanned
+
+jetty-7.1.5.v20100705
+ + Update ecj to 3.6 Helios release drop
+ + 288194 Add blacklist/whitelist to ProxyServlet and ProxyHandler
+ + 296570 EOFException for HttpExchange when HttpClient.stop called.
+ + 311550 The WebAppProvider should allow setTempDirectory
+ + 316449 Websocket disconnect fix
+ + 316584 Exception on startup if temp path has spaces and extractWAR=false
+ + 316597 Removed null check and fixed name in Resource#hrefEncodeURI
+ + 316970 jetty.sh fails to find JETTY_HOME in standard directories
+ + 316973 jetty.sh claims java installation is invalid
+ + 316976 removed quotes of JAVA_OPTIONS in jetty.sh
+ + 317019 Date HTTP header not sent for HTTP/1.0 requests
+ + 317759 Allow roles and constraints to be added after init
+ + 317906 OPTIONS correctly handles TRACE
+ + 318308 Correct quoting of unicode control characters
+ + 318470 unboxing NPE protection in HttpConnection
+ + 318551 Optional uncheck Printwriter
+ + JETTY-1237 Save local/remote address to be available after close
+ + 317007 Unable to run Jetty OSGi when
+   -Dosgi.compatibility.bootdelegation=false
+ + 316909 CNFE: org.xml.sax.SAXException on org.eclipse.jetty.osgi.boot start
+   with jsp fragment
+ + 317231 Ability to configure jetty with a fragment bundle that contains
+   etc/jetty.xml
+ + 319060 Support web-bundles that are not expanded (bundle is zipped)
+
+jetty-6.1.25 - 26 July 2010
+ + Jetty-6 is now in maintenance mode.
+ + JETTY-1212 Long content lengths
+ + JETTY-1214 Avoid ISE when scavenging invalid session
+ + JETTY-1223 DefaultServlet: NPE when setting relativeResourceBase and
+   resourceBase is not set
+ + JETTY-1226 javax.activation needs to be listed in the system classes
+ + JETTY-1237 Remember local/remote details of endpoint
+ + JETTY-1251 protected against closed selector
+ + COMETD-112 if two threads create the same channel, then create events may
+   occur after subscribe events
+ + 320264 Removed duplicate mime.property entries
+
+jetty-7.1.4.v20100610
+ + 298551 SslSocketConnector does not need keystore stream
+ + 295715 AbstractSessionManager decoupled from Context
+ + 292326 Stop continuations if server is stopped.
+ + 292814 Make QoSFilter and DoSFilter JMX manageable
+ + 293222 Improve request log to handle/show asynchronous latency
+ + 294212 Can not customize session cookie path
+ + 301608 Deregister shutdown hooks
+ + 302350 org.eclipse.jetty.server.NCSARequestLog is missing JavaDoc
+ + 303661 jetty.sh failes if JETTY_HOME is not writeable
+ + 304100 Better document JMX setup in jetty-jmx.xml
+ + 305300 AsyncContext.start dispatches runnable
+ + 314299 Create test harness for JDBCLoginService
+ + 314581 Implement the Sec-Websocket handshake
+ + 315190 CrossOriginFilter avoid headers not understood by WebSocket
+ + 315687 included init script fails to test for JETTY_HOME as empty
+ + 315715 Improved Cookie version handling. Server.setMaxCookieVersion
+ + 315744 Fixed STOP.PORT and STOP.KEY in start.jar
+ + 315748 Removed --fromDaemon from start.jar (replaced with --daemon)
+ + 315925 Improved context xml configuration handling
+ + 315995 Incorrect package name in system classes list
+ + 316119 Fixed maxIdleTime for SocketEndPoint
+ + 316254 Implement @DeclareRoles
+ + 316334 Breaking change on org.eclipse.jetty.client.HttpExchange
+ + 316399 Debug output in MultiPartFilter
+ + 316413 Restarting webapp for packed war fails
+ + 316557 OSGi HttpService failure due to undeployed context handlers
+ + JETTY-547 Delay close after shutdown until request read
+ + JETTY-1231 Support context request log handler
+
+jetty-7.1.3.v20100526
+ + 296567 HttpClient RedirectListener handles new HttpDestination
+ + 297598 JDBCLoginService uses hardcoded credential class
+ + 305898 Websocket handles query string in URI
+ + 307457 Exchanges are left unhandled when connection is lost
+ + 313205 Unable to run test-jdbc-sessions tests
+ + 314177 JSTL support is broken
+ + 314009 jetty.xml configuration file on command line
+ + 314459 support maven3 for builds
+
+jetty-7.1.2.v20100523
+ + 308866 Update test suite to JUnit4 - Module jetty-util
+ + 312948 Recycle SSL crypto buffers
+ + 313196 randomly allocate ports for session test.
+ + 313278 Implement octet ranges in IPAccessHandler
+ + 313336 secure websockets
+ + 314009 updated README.txt
+ + Update links to jetty website and wiki on test webapp
+
+jetty-7.1.1.v20100517
+ + 302344 Make the list of available contexts if root context is not configured
+   optional
+ + 304803 Remove TypeUtil Integer and Long caches
+ + 306226 HttpClient should allow changing the keystore and truststore type
+ + 308857 Update test suite to JUnit4 - Module jetty-jndi
+ + 308856 Update test suite to JUnit4 - Module jetty-jmx
+ + 308860 Update test suite to JUnit4 - Module jetty-rewrite
+ + 308850 Update test suite to JUnit4 - Module jetty-annotations
+ + 308853 Update test suite to JUnit4 - Module jetty-deploy
+ + 308854 Update test suite to JUnit4 - Module jetty-http
+ + 308859 Update test suite to JUnit4 - Module jetty-policy
+ + 308858 Update test suite to JUnit4 - Module jetty-plus
+ + 308863 Update test suite to JUnit4 - Module jetty-servlet
+ + 308855 Update test suite to JUnit4 - Module jetty-io
+ + 308862 Update test suite to JUnit4 - Module jetty-server
+ + 308867 Update test suite to JUnit4 - Module jetty-webapp
+ + 310918 Fixed write blocking for client HttpConnection
+ + 312526 Protect shutdown thread initialization during shutdown
+
+jetty-7.1.0 - 05 May 2010
+ + 306353 fixed cross context dispatch to root context.
+ + 311154 Added deprecated StringBuffer API for backwards compatibility
+ + 311554 Protect shutdown thread from Server#doStop
+ + 312243 Optimized timeout handling
+
+jetty-7.1.0.RC1 - 05 May 2010
+ + 286889 Allow System and Server classes to be set on Server instance and when
+   applied to all webapps
+ + 291448 SessionManager has isCheckingRemoteSessionIdEncoding
+ + 296650 JETTY-1198 reset idle timeout on request body chunks
+ + 297104 HTTP CONNECT does not work correct with SSL destinations
+ + 306782 Close connection when expected 100 continues is not sent
+ + 308848 Update test suite to JUnit4 - Module jetty-ajp
+ + 308861 Update test suite to JUnit4 - Module jetty-security
+ + 308864 Update test suite to JUnit4 - Module jetty-servlets
+ + 308865 Update test suite to JUnit4 - Module jetty-start
+ + 308868 Update test suite to JUnit4 - Module jetty-websocket
+ + 308869 Update test suite to JUnit4 - Module jetty-xml
+ + 309153 Hide extracted WEB-INF/lib when running a non-extracted war
+ + 309369 Added WebSocketLoadTest
+ + 309686 Fixed response buffers usage
+ + 310094 Improved start.jar options handling and configs
+ + 310382 NPE protection when WAR is not a file
+ + 310562 SslSocketConnector fails to start if excludeCipherSuites is set
+ + 310634 Get the localport when opening a server socket.
+ + 310703 Update test suite to JUnit4 - Module tests/test-integration
+ + 310918 Synchronize content exchange
+ + 311154 Use Appendable in preference to StringBuilder/StringBuffer in APIs
+ + 311362 Optional org.eclipse.jetty.util.log.stderr.SOURCE
+ + JETTY-1030 Improve jetty.sh script
+ + JETTY-1142 Replace Set-Cookies with same name
+
+jetty-7.1.0.RC0 - 27 April 2010
+ + 294563 Websocket client connection
+ + 297104 Improve handling of CONNECT method
+ + 306349 ProxyServlet does not work unless deployed at /
+ + 307294 Add AbstractLifeCycle.AbstractLifeCycleListener implementation
+ + 307847 Fixed combining mime type parameters
+ + 307898 Handle large/async websocket messages
+ + 308009 ObjectMBean incorrectly casts getTargetException() to Exception
+ + 308420 convert jetty-plus.xml to use DeploymentManager
+ + 308925 Protect the test webapp from remote access
+ + 309466 Removed synchronization from StdErrLog
+ + 309765 Added JSP module
+ + 310051 _configurationClasses now defaults to null in WebAppContext
+ + 310094 Improved start.jar usage and config files
+ + 310431 Default ErrorHandler as server Bean
+ + 310467 Allow SocketConnector to create generic Connection objects
+ + 310603 Make Logger interface consistent
+ + 310605 Make a clean room implementation of the JSP logger bridge
+ + Add AnnotationConfiguration to jetty-plus.xml
+ + Fix jetty-plus.xml reference to addLifeCycle
+ + JETTY-1200 SSL NIO Endpoint wraps non NIO buffers
+ + JETTY-1202 Use platform default algorithm for SecureRandom
+ + Merged 7.0.2.v20100331
+ + Add NPE protection to ContainerInitializerConfiguration
+ + Temporarily remove jetty-osgi module to clarify jsp version compatibility
+ + JETTY-1212 handle long content lengths
+ + JETTY-1214 avoid ISE when scavenging invalid session
+ + JETTY-903 Stop both caches
+
+jetty-7.0.2.v20100331 - 31 March 2010
+ + 297552 Don't call Continuation timeouts from acceptor tick
+ + 298236 Additional unit tests for jetty-client
+ + 306783 NPE in StdErrLog when Throwable is null
+ + 306840 Suppress content-length in requests with no content
+ + 306880 Support for UPGRADE in HttpClient
+ + 306884 Suspend with timeout <=0 never expires
+ + 306782 httpbis interpretation of 100 continues. Body never skipped
+ + 307589 updated servlet 3.0 continuations for final API
+ + Take excess logging statements out of startup
+ + Ensure webapps with no WEB-INF don't scan WEB-INF/lib
+ + Allow Configuration array to be set on Server instance for all web apps
+
+jetty-6.1.24 - 21 April 2010
+ + JETTY-903 Stop both caches
+ + JETTY-1198 reset idle timeout on request body chunks
+ + JETTY-1200 SSL NIO Endpoint wraps non NIO buffers
+ + JETTY-1211 SetUID loadlibrary name and debug
+ + 308925 Protect the test webapp from remote access
+ + COMETD-99 ClientImpl logs exceptions in listeners with "debug" level
+ + COMETD-100 ClientImpl logs "null" as clientId
+ + COMETD-107 Reloading the application with reload extension does not fire
+   /meta/connect handlers until long poll timeout expires
+ + Upgraded to cometd 1.1.1 client
+
+jetty-6.1.23 - 02 April 2010
+ + JSON parses NaN as null
+ + Updated JSP to 2.1.v20091210
+ + COMETD-28 Improved concurrency usage in Bayeux and channel handling
+ + COMETD-46 reset ContentExchange content on resend
+ + COMETD-58 Extension.rcv() return null causes NPE in
+   AbstractBayeux.PublishHandler.publish
+ + COMETD-59 AcknowledgeExtension does not handle null channel in Message
+ + COMETD-62 Delay add listeners until after client construction
+ + 296569 removeLifeCycleListener() has no effect
+ + 292800 ContextDeployer - recursive setting is undone by FilenameFilter
+ + 300178 HttpClients opens too many connections that are immediately closed
+ + 304658 Inconsistent Expires date format in Set-Cookie headers with maxAge=0
+ + 304698 org.eclipse.jetty.http.HttpFields$DateGenerator.formatCookieDate()
+   uses wrong (?) date format
+ + 306331 Session manager is kept after call to doScope
+ + 306840 suppress content-length in requests without content
+ + Remove references to old content in HttpClient client tests for www.sun.com
+ + JETTY-875 Allow setting of advice field in response to Handshake
+ + JETTY-983 Range handling cleanup
+ + JETTY-1133 Handle multiple URL ; parameters
+ + JETTY-1134 BayeuxClient: Connect msg should be sent as array
+ + JETTY-1149 transient should be volatile in AbstractLifeCycle
+ + JETTY-1153 System property for UrlEncoded charset
+ + JETTY-1155 HttpConnection.close notifies HttpExchange
+ + JETTY-1156 SSL blocking close with JVM Bug busy key fix
+ + JETTY-1157 Don't hold array passed in write(byte[])
+ + JETTY-1158 NPE in StdErrLog when Throwable is null
+ + JETTY-1161 An Extension that measures round-trip delay for cometd messages.
+ + JETTY-1162 Add support for async/sync message delivery to BayeuxClient
+ + JETTY-1163 AJP13 forces 8859-1 encoding
+ + JETTY-1168 Don't hold sessionIdManager lock when invalidating sessions
+ + JETTY-1170 NPE on client when server-side extension returns null
+ + JETTY-1174 Close rather than finish Gzipstreams to avoid JVM leak
+ + JETTY-1175 NPE in TimesyncExtension
+ + JETTY-1176 NPE in StatisticsExtension if client is null
+ + JETTY-1177 Allow error handler to set cacheControl
+ + JETTY-1178 Make continuation servlet to log the incoming JSON in case of
+   parsing errors
+ + JETTY-1180 Extension methods are wrongly called
+ + JETTY-1182 COMETD-76 do not lock client while sending messages.
+ + JETTY-1183 AcknowledgedMessagesClientExtension does not handle correctly
+   message resend when client long polls again
+ + JETTY-1186 Better document JMX setup in jetty-jmx.xml
+ + JETTY-1188 Null old jobs in QueuedThreadPool
+ + JETTY-1191 Limit size of ChannelId cache
+ + JETTY-1192 Fixed Digested POST and HttpExchange onRetry
+ + JETTY-1193 Exception details are lost in AbstractCometdServlet.getMessages
+ + JETTY-1195 Coalesce buffers in ChannelEndPoint.flush()
+ + JETTY-1196 Enable TCP_NODELAY by default in client connectors
+ + JETTY-1197 SetUID module test fails when using Java 1.6 to build
+ + JETTY-1199 FindBugs cleanups
+ + JETTY-1205 Memory leak in browser-to-client mapping
+ + JETTY-1207 NPE protection in FormAuthenticator
+ + JETTY-1202 Use platfrom default algorithm for SecureRandom
+
+jetty-7.0.2.RC0
+ + JSON parses NaN as null
+ + 290765 Reset input for HttpExchange retry.
+ + 292799 WebAppDeployer - start a started context?
+ + 292800 ContextDeployer - recursive setting is undone by FilenameFilter
+ + 294799 when configuring a webapp, don't look for WEB-INF/jetty6-web.xml
+ + 296569 removeLifeCycleListener() has no effect
+ + 296765 JMX Connector Server and ShutdownThread
+ + 297421 Hide server/system classes from WebAppClassLoader.getResources
+ + 297783 Handle HEAD reponses in HttpClient
+ + 298144 Unit test for jetty-client connecting to a server that uses Basic
+   Auth
+ + 298145 Reorganized test harness to separate the HTTP PUT and HTTP GET test
+   URLs
+ + 298234 Unit test for jetty-client handling different HTTP error codes
+ + 298667 DeploymentManager uses ContextProvider and WebAppProvider
+ + 299455 Enum support in JSONPojoConvertor
+ + 300178 HttpClients opens too many connections that are immediately closed
+ + 300733 Jars from lib/ext are not visible for my web application
+ + 300933 AbstractConnector uses concurrent objects for stats
+ + 301089 Improve statistics available in StatisticsHandler and
+   AbstractConnector
+ + 302018 Improve statistics available in AbstractSessionHandler
+ + 302198 Rename HttpClient authorization classes to Authentication
+ + 302244 invalid configuration boolean conversion in FormAuthenticator
+ + 302246 redirect loop using form authenticator
+ + 302556 CrossOriginFilter does not work correctly when
+   Access-Control-Request-Headers header is not present
+ + 302669 WebInfConfiguration.unpack() unpacks WEB-INF/* from a
+   ResourceCollection, breaking JSP reloading with ResourceCollections
+ + 303526 Added include cyphers
+ + 304307 Handle ;jsessionid in FROM Auth
+ + 304532 Skip some tests on IBM JVMs until resolved
+ + 304658 Inconsistent Expires date format in Set-Cookie headers with maxAge=0
+ + 304698 org.eclipse.jetty.http.HttpFields$DateGenerator.formatCookieDate()
+   uses wrong (?) date format
+ + 304781 Reset HttpExchange timeout on slow request content.
+ + 304801 SSL connections FULL fix
+ + 306330 Flush filter chain cache after Invoker servlet
+ + 306331 Session manager is kept after call to doScope
+ + JETTY-776 Make new session-tests module to concentrate all reusable session
+   clustering test code
+ + JETTY-910 Allow request listeners to access session
+ + JETTY-983 Range handling cleanup
+ + JETTY-1151 JETTY-1098 allow UTF-8 with 0 carry bits
+ + JETTY-1153 System property for UrlEncoded charset
+ + JETTY-1155 HttpConnection.close notifies HttpExchange
+ + JETTY-1156 SSL blocking close with JVM Bug busy key fix
+ + JETTY-1157 Don't hold array passed in write(byte[])
+ + JETTY-1163 AJP13 forces 8859-1 encoding
+ + JETTY-1177 Allow error handler to set cacheControl
+ + JETTY-1179 Persistant session tables created on MySQL use wrong datatype
+ + JETTY-1184 shrink thread pool even with frequent small jobs
+ + JETTY-1133 Handle multiple URL ; parameters
+ + JETTY-1174 Close rather than finish Gzipstreams to avoid JVM leak
+ + JETTY-1192 Fixed Digested POST
+ + JETTY-1199 FindBugs cleanups
+ + COMETD-46 reset ContentExchange response content on resend
+ + Added IPAccessHandler
+ + Updated Servlet3Continuation to final 3.0.20100224
+ + 305997 Coalesce buffers in ChannelEndPoint.flush()
+ + 306028 Enable TCP_NODELAY by default in client connectors
+
+jetty-8.0.0.M0 - 28 February 2010
+ + Updated servlet 3.0 spec 20100224
+ + Merged 7.0.1.v20091116
+ + Updated to cometd 1.0.1
+
+jetty-7.0.1.v20091125 - 25 November 2009
+ + 274251 DefaultServlet supports exact match mode.
+ + 288401 HttpExchange.cancel() Method Unimplemented
+ + 289027 deobfuscate HttpClient SSL passwords
+ + 289265 Test harness for async input
+ + 289959 Improved ContextDeployer configuration
+ + 289960 start.jar assumes command line args are configs
+ + 291019 Fix default DEBUG option; "-D.DEBUG=true" now works
+ + 291340 Race condition in onException() notifications
+ + 291543 make bin/*.sh scripts executable in distribution
+ + 291589 Update jetty-rewrite demo
+ + 292642 Fix errors in embedded Jetty examples
+ + 292825 Continuations ISE rather than ignore bad transitions
+ + 292546 Proactively enforce HttpClient idle timeout
+ + 293222 Improved StatisticsHandler for async
+ + 293506 Unable to use jconsole with Jetty when running with security manager
+ + 293557 Add "jad" mime mapping
+ + 294154 Patched jetty-osgi
+ + 294224 HttpClient timeout setting has no effect when connecting to host
+ + 294345 Support for HTTP/301 + HTTP/302 response codes
+ + 294563 Initial websocket implementation
+ + JETTY-937 More JVM bug work arounds. Insert pause if all else fails
+ + JETTY-983 Send content-length with multipart ranges
+ + JETTY-1114 unsynchronised WebAppClassloader.getResource(String)
+ + JETTY-1121 Merge Multipart query parameters
+ + JETTY-1122 Handle multi-byte utf that causes buffer overflow
+ + JETTY-1125 TransparentProxy incorrectly configured for test webapp
+ + JETTY-1129 Filter control characters out of StdErrLog
+ + JETTY-1135 Handle connection closed before accepted during JVM bug work
+   around
+ + JETTY-1144 fixed multi-byte character overflow
+ + JETTY-1148 Reset partially read request reader.
+ + COMETD-34 Support Baeyux MBean
+ + Fixed XSS issue in CookieDump demo servlet.
+ + Improved start.jar usage text for properties
+ + Promoted Jetty Centralized Logging from Sandbox
+ + Promoted Jetty WebApp Verifier from Sandbox
+ + Refactored continuation test harnessess
+ + Fixed client abort asocciation
+ + CQ-3581 jetty OSGi contribution
+ + Moved centralized logging and verifier back to sandbox
+ + CVE-2009-3555 Prevent SSL renegotiate for SSL vulnerability
+ + 295421 Cannot reset() a newly created HttpExchange: IllegalStateException 0
+   => 0
+ + 295562 CrossOriginFilter does not work with default values in Chrome and
+   Safari
+
+jetty-7.0.0.v20091005 - 05 October 2009
+ + 291340 Race condition in onException() notifications
+
+jetty-6.1.21 - 22 September 2009
+ + JETTY-719 Document state machine of jetty http client
+ + JETTY-933 State == HEADER in client
+ + JETTY-936 Improved servlet matching and optimized
+ + JETTY-1038 ChannelId.isParentOf returns the wrong result
+ + JETTY-1061 Catch exceptions from cometd listeners
+ + JETTY-1072 maven plugin handles context path not as documented
+ + JETTY-1080 modified previous fix for windows
+ + JETTY-1084 HEAD command not setting content-type in response under certain
+   circumstances
+ + JETTY-1090 resolve inifinte loop condition for webdav listener
+ + JETTY-1092 MultiPartFilter can be pushed into infinite loop
+ + JETTY-1093 Request.toString throws exception when size exceeds 4k
+ + JETTY-1098 Default form encoding is UTF8
+ + JETTY-1099 Improve cookie handling in BayeuxClient
+ + JETTY-1100 extend setuid feature to allow setting max open file descriptors
+ + JETTY-1102 Wrong usage of deliver() in private chat messages
+ + JETTY-1108 SSL EOF detection
+ + JETTY-1109 Improper handling of cookies in Terracotta tests
+ + JETTY-1112 Response fails if header exceeds buffer size
+ + JETTY-1113 IllegalStateException when adding servlet filters
+   programmatically
+ + JETTY-1114 Unsynchronize webapp classloader getResource
+ + 282543 HttpClient SSL buffer size fix
+ + 288055 fix jetty-client for failed listener state machine
+ + 288153 reset exchange when resending
+ + 288182 PUT request fails during retry
+ + Fix DefaultServletTest for windows
+ + Update Jetty implementation of com.sun.net.httpserver.*
+ + Include tmp directory sweeper in build
+ + Streamline jetty-jboss build, update sar to QueuedThreadPool
+
+jetty-7.0.0.RC6 - 21 September 2009
+ + Fixed XSS issue in CookieDump demo servlet.
+ + 289958 StatisticsServlet incorrectly adds StatisticsHandler
+ + 289960 start.jar assumes command line args are configs
+ + 290081 Eager consume LF after CR
+ + 290761 HttpExchange isDone handles intercepted events.
+ + JETTY-719 Document state machine of jetty http client
+ + JETTY-780 CNFE during startup of webapp with spring-context >= 2.5.1
+ + JETTY-936 274251 Improved servlet matching and optimized'
+ + JETTY-1080 modify previous fix to work on windows
+ + JETTY-1084 HEAD command not setting content-type in response under certain
+   circumstances
+ + JETTY-1086 Use UncheckedPrintWriter & cleaned up HttpStatus.Code usage
+ + JETTY-1090 resolve potential infinite loop with webdav listener
+ + JETTY-1092 MultiPartFilter can be pushed into infinite loop
+ + JETTY-1093 Request.toString throws exception when size exceeds 4k
+ + JETTY-1098 Default form encoding is UTF8
+ + JETTY-1101 Updated servlet3 continuation constructor
+ + JETTY-1105 Custom error pages aren't working
+ + JETTY-1108 SSL EOF detection
+ + JETTY-1112 Response fails if header exceeds buffer size
+ + JETTY-1113 IllegalStateException when adding servlet filters
+   programmatically
+ + 280723 Add non blocking statistics handler
+ + 282543 HttpClient SSL buffer size fix
+ + 283357 org.eclipse.jetty.server.HttpConnectionTest exceptions
+ + 288055 jetty-client fails to resolve failed resolution attempts correctly
+ + 288153 jetty-client resend doesn't reset exchange
+ + 288466 LocalConnector is not thread safe
+ + 288514 AbstractConnector does not handle InterruptedExceptions on shutdown
+ + 288772 Failure to connect does not set status to EXCEPTED
+ + 289146 formalize reload policy functionality
+ + 289156 jetty-client: no longer throw runtime exception for bad authn details
+ + 288182 PUT request fails during retry
+ + 289221 HttpExchange does not timeout when using blocking connector
+ + 289285 org.eclipse.jetty.continuation 7.0.0.RC5 imports the
+   org.mortbay.util.ajax package
+ + 289686 HttpExchange.setStatus() has too coarse synchronization
+ + Tweak DefaultServletTest under windows
+ + Copy VERSION.txt to distro
+ + Remove printlns from jetty-plus
+
+jetty-6.1.20 - 27 August 2009
+ + JETTY-838 Don't log and throw
+ + JETTY-874 Better error on full header.
+ + JETTY-960 Support ldaps
+ + JETTY-1046 maven-jetty-jspc-plugin keepSources takes affect only in
+   packageRoot
+ + JETTY-1057 XSS error page
+ + JETTY-1065 Add RedirectRegexRule to provide match/replace/group redirect
+   support
+ + JETTY-1066 Send 400 error for request URI parse exceptions
+ + JETTY-1068 Avoid busy flush of async SSL
+ + JETTY-1069 Adjust Bayeux Java client backoff algorithm
+ + JETTY-1070 Java Bayeux Client not sending /meta/disconnect on stop
+ + JETTY-1074 JMX thread manipulation
+ + JETTY-1077 HashSSORealm shares Principals between UserRealms
+ + JETTY-1078 Automatic JSON Pojo Conversion
+ + JETTY-1079 ResourceCollection.toString() can throw IllegalStateException
+ + JETTY-1080 Ignore files that would be extracted outside the destination
+   directory when unpacking WARs
+ + JETTY-1081 Handle null content type in GzipFilter
+ + JETTY-1084 Disable GzipFilter for HEAD requests
+ + JETTY-1085 Allow url sessionID if cookie invalid
+ + JETTY-1086 Added UncheckedPrintWriter to avoid ignored EOFs
+ + JETTY-1087 Chunked SSL non blocking input
+ + JETTY-1098 Upgrade jsp to SJSAS-9_1_1-B60F-07_Jan_2009
+ + 283513 Check endp.isOpen when blocking read
+ + 283818 fixed merge of forward parameters
+ + 285006 Fixed NPE in AbstractConnector during shutdown
+ + 286535 ContentExchange status code
+ + 286911 Clean out cache when recycling HTTP fields
+ + COMETD-7 max latency config for lazy messages
+ + Added getSubscriptions to cometd client
+ + Made unSubscribeAll public on cometd client
+ + Removed clearing of queue in unSubscribeAll for cometd client
+ + Update test-jndi and test-annotation examples for atomikos 3.5.5
+ + Clarified cometd interval timeout and allow per client intervals
+ + Update Main.main method to call setWar
+ + Added DebugHandler
+
+jetty-7.0.0.RC5 - 27 August 2009
+ + 286911 Clean out cache when recycling HTTP fields
+ + JETTY-838 Don't log and throw
+ + JETTY-874 Better header full warnings
+ + JETTY-960 Support for ldaps
+ + JETTY-1081 Handle null content type in GzipFilter
+ + JETTY-1084 Disable GzipFilter for HEAD requests
+ + JETTY-1085 Allow url sessionID if cookie invalid
+ + JETTY-1086 Added UncheckedPrintWriter to avoid ignored EOFs
+ + JETTY-1087 Chunked SSL non blocking input
+ + 287496 Use start.ini always and added --exec
+ + 287632 FilterContinuations for blocking jetty6
+
+jetty-6.1.19 - 01 July 2009
+ + JETTY-799 shell script for jetty on cygwin
+ + JETTY-863 Non blocking stats handler
+ + JETTY-937 Further Improvements for sun JVM selector bugs
+ + JETTY-970 BayeuxLoadGenerator latency handling
+ + JETTY-1011 Grizzly uses queued thread pool
+ + JETTY-1028 jetty:run plugin should check for the web.xml from the overlays
+   if not found in src/main/webapp/WEB-INF/
+ + JETTY-1029 Handle quoted cookie paths
+ + JETTY-1031 Handle large pipeline
+ + JETTY-1033 jetty-plus compiled with jdk1.5
+ + JETTY-1034 Cookie parsing
+ + JETTY-1037 reimplemented channel doRemove
+ + JETTY-1040 jetty.client.HttpConnection does not handle non IOExceptions
+ + JETTY-1042 Avoid cookie reuse on shared connection
+ + JETTY-1044 add commons-daemon support as contrib/start-daemon module
+ + JETTY-1045 Handle the case where request.PathInfo() should be "/*"
+ + JETTY-1046 maven-jetty-jspc-plugin keepSources takes affect only in
+   packageRoot
+ + JETTY-1047 Cometd client can grow cookie headers
+ + JETTY-1048 Default servlet can handle partially filtered large static
+   content
+ + JETTY-1049 Improved transparent proxy usability
+ + JETTY-1054 Avoid double deploys
+ + JETTY-1055 Cookie quoting
+ + JETTY-1057 Error page stack trace XSS
+ + JETTY-1058 Handle trailing / with aliases on
+ + JETTY-1062 Don't filter cometd message without data
+
+jetty-7.0.0.RC4 - 18 August 2009
+ + 286185 Implement ability for JSON implementation to automatically register
+   convertors
+ + Added discoverable start options
+ + 286535 ContentExchange status code
+ + 285891 SessionAuthentication is serializable
+ + JETTY-1079 ResourceCollection.toString
+ + 279820 Fixed HotSwapHandler
+ + JETTY-1080 Ignore files that would be extracted outside the destination
+   directory when unpacking WARs
+ + JETTY-1057 XSS error page
+
+jetty-7.0.0.RC3 - 07 August 2009
+ + 277403 remove system properties
+ + JETTY-1074 JMX thread manipulation
+ + Improved deferred authentication handling
+ + 285697 extract parameters if dispatch has query
+ + 282447 concurrent destinations in HttpClient
+ + 283172 fix Windows build, broken on directory creation with the
+   DefaultServlet
+ + 283375 additional error-checking on SSL connector passwords to prevent NPE
+ + 283513 Check endp.isOpen when blocking read
+
+jetty-7.0.0.RC2 - 29 June 2009
+ + 283844 Webapp / TLD errors are not clear
+ + 283375 improved extensibility of SSL connectors
+ + 283818 fixed merge of forward parameters
+ + backport jetty-8 annotation parsing to jetty-7
+ + Disassociate method on IdentityService
+ + 284510 Enhance jetty-start for diagnosis and unit testing
+ + 284475 update jetty.sh for new OPTIONS syntax
+ + Added DebugHandler
+ + Added JavaUtilLog for Jetty logging to java.util.logging framework
+ + 284981 Implement a cross-origin filter
+ + Improved handling of overlays and resourceCollections
+ + 285006 fix AbstractConnector NPE during shutdown.
+
+jetty-7.0.0.RC1 - 15 June 2009
+ + JETTY-1066 283357 400 response for bad URIs
+ + JETTY-1068 Avoid busy flush of async SSL
+ + 283344 Startup on windows is broken
+
+jetty-7.0.0.RC0 - 08 June 2009
+ + JETTY-967 create standalone build for PKCS12Import at codehaus
+ + JETTY-1056 update jetty-ant module for Jetty 7 at codehaus trunk
+ + JETTY-1058 Handle trailing / with aliases
+ + 280843 Buffer pool uses isHeader
+ + 271535 Adding integration tests, and enabling RFC2616 tests
+ + 281287 Handle date headers before 1 Jan 1970
+ + 282807 Better handling of 100 continues if response committed.
+
+jetty-7.0.0.M4 - 01 June 2009
+ + 281059 NPE in QTP with debug on
+ + JETTY-799 shell script for jetty on cygwin
+ + JETTY-1031 Handle large pipeline
+ + JETTY-1034 Cookie parsing
+ + JETTY-1042 Prevent cookie leak between shared connection
+ + JETTY-1048 Fix for large partially filtered static content
+ + JETTY-1049 Improved transparent proxy usability
+ + JETTY-1054 Avoid double deploys
+ + JETTY-1055 Cookie quoting
+ + JETTY-1057 Error page stack trace XSS
+
+jetty-7.0.0.M3 - 20 June 2009
+ + fixed race with expired async listeners
+ + refactored configuration mechanism
+ + added WebAppContext.setConfigurationDiscovered for servlet 3.0 features
+ + 274251 Allow dispatch to welcome files that are servlets (configurable)
+ + 277403 Cleanup system property usage.
+ + 277798 Denial of Service Filter
+ + Portable continuations for jetty6 and servlet3
+ + Refactored continuations to only support response wrapping
+ + Added ContinuationThrowable
+ + 276545 Quoted cookie paths
+ + 279725 Support 100 and 102 expectations
+ + Refactored AbstractBuffers to HttpBuffers for performance
+ + Numerous cleanups from static code analysis
+ + 280707 client.HttpConnection does not catch and handle non-IOExceptions
+ + 281470 Handle the case where request.PathInfo() should be "/*"
+
+jetty-7.0.0.M2 - 18 May 2009
+ + JETTY-937 Work around Sun JVM bugs
+ + JETTY-941 Linux chkconfig hint
+ + JETTY-959 CGI servlet doesn't kill the CGI in case the client disconnects
+ + JETTY-980 Fixed ResourceHandler ? handling, and bad URI creation in listings
+ + JETTY-996 Make start-stop-daemon optional
+ + 273767 Update to use geronimo annotations spec 1.1.1
+ + JETTY-1003 java.lang.IllegalArgumentException: timeout can't be negative
+ + JETTY-1004 CERT VU#402580 Canonical path handling includes ? in path segment
+ + JETTY-1013 MySql Error with JDBCUserRealm
+ + JETTY-1014 Enable start-stop-daemon by default on jetty.sh
+   (START_STOP_DAEMON=1)
+ + JETTY-1015 Reduce BayeuxClient and HttpClient lock contention
+ + JETTY-1020 ZipException in org.mortbay.jetty.webapp.TagLibConfiguration
+   prevents all contexts from being loaded
+ + 275396 Added ScopedHandler to set servlet scope before security handler
+
+jetty-6.1.18 - 16 May 2009
+ + JETTY-937 Improved work around sun JVM selector bugs
+ + JETTY-1004 CERT VU#402580 Canonical path handling includes ? in path segment
+ + JETTY-1008 ContinuationBayeux destroy is called
+ + JETTY-1013 MySql Error with JDBCUserRealm
+ + JETTY-1014 Enable start-stop-daemon by default on jetty.sh
+   (START_STOP_DAEMON=1)
+ + JETTY-1015 Reduce BayeuxClient and HttpClient lock contention
+ + JETTY-1017 HttpDestination has too coarse locking
+ + JETTY-1018 Denial of Service Filter
+ + JETTY-1020 ZipException in org.mortbay.jetty.webapp.TagLibConfiguration
+   prevents all contexts from being loaded
+ + JETTY-1022 Removed several 1.5isms
+
+jetty-5.1.15 - 18 May 2009
+ + JETTY-418 synchronized load class
+ + JETTY-1004 CERT VU402580 Canonical path handling includes ? in path segment
+ + Fixes for CERT438616-CERT237888-CERT21284
+
+jetty-6.1.17 - 30 April 2009
+ + JETTY-936 Make optional dispatching to welcome files as servlets
+ + JETTY-937 Work around sun JVM selector bugs
+ + JETTY-941 Linux chkconfig hint
+ + JETTY-957 Reduce hardcoded versions
+ + JETTY-980 Security / Directory Listing XSS present
+ + JETTY-982 Make test-jaas-webapp run with jetty:run
+ + JETTY-983 Default Servlet sets accept-ranges for cached/gzipped content
+ + JETTY-988 X-Forwarded-Host has precedence over X-Forwarded-Server
+ + JETTY-989 GzipFilter handles addHeader
+ + JETTY-990 Async HttpClient connect
+ + JETTY-992 URIUtil.encodePath encodes markup characters
+ + JETTY-996 Make start-stop-daemon optional
+ + JETTY-997 Remove jpackage-utils dependency on rpm install
+ + JETTY-985 Allow listeners to implement both interfaces
+ + JETTY-1000 Avoided needless 1.5 dependency
+ + JETTY-1002 cometd-api to 1.0.beta8
+ + JETTY-1003 java.lang.IllegalArgumentException: timeout can't be negative
+ + JETTY-1004 CERT VU#402580 Canonical path handling includes ? in path segment
+ + JETTY-1006 Resume meta connect on all XD messages
+
+jetty-7.0.0.M1 - 22 April 2009
+ + 271258 FORM Authentication dispatch handling avoids caching
+ + Initial support for LoginService.logout
+ + Removed HTTPConnection specifics from connection dispatching
+ + JETTY-695 Handler dump
+ + Reworked authentication for deferred authentication
+ + Reworked JMX for new layout
+ + JETTY-983 DefaultServlet generates accept-ranges for cached/gzip content
+ + 273011 JETTY-980 JETTY-992 Security / Directory Listing XSS present
+ + 271536 Add support to IO for quietly closing Readers / Writers
+ + 273101 Fix DefaultServletTest XSS test case
+ + 273153 Test for Nested references in DispatchServlet
+
+jetty-6.1.16 - 01 April 2009
+ + JETTY-702 Create "jetty-tasks.xml" for the Ant plugin
+ + JETTY-899 Standardize location for configuration files which go into etc
+ + JETTY-936 Allow dispatch to welcome files that are servlets
+ + JETTY-944 Lazy messages don't prevent long polls waiting
+ + JETTY-946 Redeploys with maven jetty plugin of webapps with overlays don't
+   work
+ + JETTY-947 Exception stops terracotta session scavenger
+ + JETTY-948 ConcurrentModificationException in TerracottaSessionManager
+   scavenger
+ + JETTY-949 Move cometd source to cometd.org project
+ + JETTY-953 SSL keystore file input stream is not being closed directly
+ + JETTY-956 SslSelectChannelConnector - password should be the default value
+   of keyPassword if not specified
+ + JETTY-959 CGI servlet doesn't kill the CGI in case the client disconnects
+ + JETTY-964 Typo in Jetty 6.1.15 Manifest - Bundle-RequiredExcutionEnvironment
+ + JETTY-972 Move cometd code back from cometd.org project (temporarily)
+ + JETTY-973 Deliver same message to a collection of cometd Clients
+
+jetty-7.0.0.M0 - 27 March 2009
+ + JETTY-496 Support inetd/xinetd through use of System.inheritedChannel()
+ + JETTY-540 Merged 3.0 Public Review changes
+ + JETTY-567 Delay in initial TLS Handshake With FireFox 3 beta5 and
+   SslSelectChannelConnector
+ + JETTY-600 Automated tests of WADI integration + upgrade to WADI 2.0
+ + JETTY-691 System.getProperty() calls ... wrap them in doPrivileged
+ + JETTY-713 Expose additional AbstractConnector methods via MBean
+ + JETTY-731 Completed DeliverListener for cometd
+ + JETTY-748 RandomAccessFileBuffer for hadoop optimization
+ + JETTY-749 Improved ArrayQueue
+ + JETTY-765 ensure stop mojo works for all execution phases
+ + JETTY-774 Improved caching of mime types with charsets
+ + JETTY-775 AbstractSessionTest remove timing related test
+ + JETTY-778 handle granular windows timer in lifecycle test
+ + JETTY-779 Fixed line feed in request log
+ + JETTY-781 Add "mvn jetty:deploy-war" for deploying a pre-assembled war
+ + JETTY-782 Implement interval advice for BayeuxClient
+ + JETTY-783 Update jetty self-signed certificate
+ + JETTY-784 TerracottaSessionManager leaks sessions scavenged in other nodes
+ + JETTY-786 Allow DataSourceUserRealm to create tables
+ + JETTY-787 Handle MSIE7 mixed encoding
+ + JETTY-788 Fix jotm for scoped jndi naming
+ + JETTY-790 WaitingContinuations can change mutex if not pending
+ + JETTY-792 TerracottaSessionManager does not unlock new session with
+   requested id
+ + JETTY-793 Fixed DataCache millisecond rounding
+ + JETTY-794 WADI integration tests fail intermittently.
+ + JETTY-795 NullPointerException in SocketConnector.java
+ + JETTY-801 Bring back 2 arg EnvEntry constructor
+ + JETTY-802 Modify the default error pages to make association with Jetty
+   clearer
+ + JETTY-804 HttpClient timeout does not always work
+ + JETTY-805 Fix jetty-jaas.xml for new UserRealm package
+ + JETTY-806 Timeout related Deadlocks in HTTP Client
+ + JETTY-807 HttpTester to handle charsets
+ + JETTY-808 cometd client demo run.sh
+ + JETTY-809 Need a way to customize WEB-INF/lib file extensions that are added
+   to the classpath
+ + JETTY-811 Allow configuration of system properties for the maven plugin
+   using a file
+ + JETTY-813 Simplify NCSARequestLog.java
+ + JETTY-814 Add org.eclipse.jetty.client.Address.toString()
+ + JETTY-816 Implement reconnect on java bayeux client
+ + JETTY-817 Aborted SSL connections may cause jetty to hang with full cpu
+ + JETTY-818 Support javax.servlet.request.ssl_session_id
+ + JETTY-821 Allow lazy loading of persistent sessions
+ + JETTY-822 Commit when autocommit=true causes error with mysql
+ + JETTY-823 Extend start.config profiles
+ + JETTY-824 Access to inbound byte statistics
+ + JETTY-825 URL decoding of spaces (+) fails for encoding not utf8
+ + JETTY-830 Add ability to reserve connections on http client
+ + JETTY-831 Add ability to stop java bayeux client
+ + JETTY-832 More UrlDecoded handling in relation to JETTY-825
+ + JETTY-834 Configure DTD does not allow <Map> children
+ + JETTY-837 Response headers set via filter are ignored for static resources
+ + JETTY-840 add default mime types to *.htc and *.pps
+ + JETTY-841 Duplicate messages when sending private message to yourself with
+   cometd chat demo
+ + JETTY-842 NPE in jetty client when no path component
+ + JETTY-843 META-INF/MANIFEST.MF is not present in unpacked webapp
+ + JETTY-844 Replace reflection with direct invocation in Slf4jLog
+ + JETTY-848 Temporary folder not fully cleanup after stop (via Sweeper)
+ + JETTY-854 JNDI scope does not work with applications in a .war
+ + JETTY-859 MultiPartFilter ignores the query string parameters
+ + JETTY-861 switched buffer pools to ThreadLocal implementation
+ + JETTY-862 EncodedHttpURI ignores given encoding in constructor
+ + JETTY-866 jetty-client test case fix
+ + JETTY-869 NCSARequestLog locale config
+ + JETTY-870 NullPointerException in Response when performing redirect to wrong
+   relative URL
+ + JETTY-871 jetty-client expires() NPE race condition fixed
+ + JETTY-876 Added new BlockingArrayQueue and new QueuedThreadPool
+ + JETTY-894 Add android .apk to mime types
+ + JETTY-897 Remove swing dependency in GzipFilter
+ + JETTY-898 Allow jetty debs to start with custom java args provided by users
+ + JETTY-899 Standardize location and build process for configuration files
+   which go into etc
+ + JETTY-890 merge jaspi branch to trunk
+ + JETTY-909 Update useragents cache
+ + JETTY-917 Change for JETTY-811 breaks systemProperties config parameter in
+   maven-jetty-plugin
+ + JETTY-922 Fixed NPE on getRemoteHost when socket closed
+ + JETTY-923 Client supports attributes
+ + JETTY-926 default location for generatedClasses of jspc plugin is incorrect
+ + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
+ + JETTY-938 Deadlock in the TerracottaSessionManager
+ + JETTY-946 Redeploys with maven jetty plugin of webapps with overlays don't
+   work
+ + JETTY-950 Fix double-printing of request URI in request log
+ + JETTY-953 SSL keystore file input stream is not being closed directly
+ + JETTY-956 SslSelectChannelConnector - password should be the default value
+   of keyPassword if not specified
+ + moved to org.eclipse packages
+ + simplified HandlerContainer API
+
+jetty-6.1.15 - 04 March 2009
+ + JETTY-931 Fix issue with jetty-rewrite.xml
+ + JETTY-934 fixed stop/start of Bayeux Client
+ + JETTY-938 Deadlock in the TerracottaSessionManager
+ + JETTY-939 NPE in AbstractConfiguration.callPreDestroyCallbacks
+ + JETTY-923 BayeuxClient uses message pools to reduce memory footprint
+ + JETTY-924 Improved BayeuxClient disconnect handling
+ + JETTY-925 Lazy bayeux messages
+ + JETTY-926 default location for generatedClasses of jspc plugin is incorrect
+
+jetty-6.1.15 - 02 March 2009
+ + JETTY-923 BayeuxClient uses message pools to reduce memory footprint
+ + JETTY-924 Improved BayeuxClient disconnect handling
+ + JETTY-925 Lazy bayeux messages
+ + JETTY-926 default location for generatedClasses of jspc plugin is incorrect
+
+jetty-6.1.15.rc4 - 19 February 2009
+ + JETTY-496 Support inetd/xinetd through use of System.inheritedChannel()
+ + JETTY-713 Expose additional AbstractConnector methods via MBean
+ + JETTY-749 Improved ack extension
+ + JETTY-811 Allow configuration of system properties for the maven plugin
+   using a file
+ + JETTY-840 add default mime types to *.htc and *.pps
+ + JETTY-848 Temporary folder not fully cleanup after stop (via Sweeper)
+ + JETTY-872 Handshake handler calls wrong extension callback
+ + JETTY-879 Support extra properties in jQuery comet implementation
+ + JETTY-802 Modify the default error pages to make association with Jetty
+   clearer
+ + JETTY-869 NCSARequestLog locale config
+ + JETTY-870 NullPointerException in Response when performing redirect to wrong
+   relative URL
+ + JETTY-878 Removed printStackTrace from WaitingContinuation
+ + JETTY-882 ChannelBayeuxListener called too many times
+ + JETTY-884 Use hashcode for threadpool ID
+ + JETTY-815 Add comet support to jQuery javascript library
+ + JETTY-887 Split configuration and handshaking in jquery comet
+ + JETTY-888 Fix abort in case of multiple outstanding connections
+ + JETTY-894 Add android .apk to mime types
+ + JETTY-898 Allow jetty debs to start with custom java args provided by users
+ + JETTY-909 Update useragents cache
+
+jetty-6.1.15.rc3 - 28 January 2009
+ + JETTY-691 System.getProperty() calls ... wrap them in doPrivileged
+ + JETTY-844 Replace reflection with direct invocation in Slf4jLog
+ + JETTY-861 switched buffer pools to ThreadLocal implementation
+ + JETTY-866 jetty-client test case fix
+
+jetty-6.1.15.rc2 - 23 January 2009
+ + adjustment to jetty-client assembly packaging
+ + JETTY-567 Delay in initial TLS Handshake With FireFox 3 beta5 and
+   SslSelectChannelConnector
+
+jetty-6.1.15.pre0 - 20 January 2009
+ + JETTY-600 Automated tests of WADI integration + upgrade to WADI 2.0
+ + JETTY-749 Reliable message delivery
+ + JETTY-794 WADI integration tests fail intermittently.
+ + JETTY-781 Add "mvn jetty:deploy-war" for deploying a pre-assembled war
+ + JETTY-795 NullPointerException in SocketConnector.java
+ + JETTY-798 Jboss session manager incompatible with LifeCycle.Listener
+ + JETTY-801 Bring back 2 arg EnvEntry constructor
+ + JETTY-802 Modify the default error pages to make association with Jetty very
+   clear
+ + JETTY-804 HttpClient timeout does not always work
+ + JETTY-806 Timeout related Deadlocks in HTTP Client
+ + JETTY-807 HttpTester to handle charsets
+ + JETTY-808 cometd client demo run.sh
+ + JETTY-809 Need a way to customize WEB-INF/lib file extensions that are added
+   to the classpath
+ + JETTY-814 Add org.eclipse.jetty.client.Address.toString()
+ + JETTY-816 Implement reconnect on java bayeux client
+ + JETTY-817 Aborted SSL connections may cause jetty to hang with full cpu
+ + JETTY-819 Jetty Plus no more jre 1.4
+ + JETTY-821 Allow lazy loading of persistent sessions
+ + JETTY-824 Access to inbound byte statistics
+ + JETTY-825 URL decoding of spaces (+) fails for encoding not utf8
+ + JETTY-827 Externalize servlet api
+ + JETTY-830 Add ability to reserve connections on http client
+ + JETTY-831 Add ability to stop java bayeux client
+ + JETTY-832 More UrlDecoded handling in relation to JETTY-825
+ + JETTY-833 Update debian and rpm packages for new jsp-2.1-glassfish jars and
+   servlet-api jar
+ + JETTY-834 Configure DTD does not allow <Map> children
+ + JETTY-837 Response headers set via filter are ignored for static resources
+ + JETTY-841 Duplicate messages when sending private message to yourself with
+   cometd chat demo
+ + JETTY-842 NPE in jetty client when no path component
+ + JETTY-843 META-INF/MANIFEST.MF is not present in unpacked webapp
+ + JETTY-852 Ensure handshake and connect retried on failure for jquery-cometd
+ + JETTY-854 JNDI scope does not work with applications in a .war
+ + JETTY-855 jetty-client uber assembly support
+ + JETTY-858 ContentExchange provides bytes
+ + JETTY-859 MultiPartFilter ignores the query string parameters
+ + JETTY-862 EncodedHttpURI ignores given encoding in constructor
+
+jetty-6.1.14 - 14 November 2008
+ + JETTY-630 jetty6-plus rpm is missing the jetty6-plus jar
+ + JETTY-748 Reduced flushing of large content
+ + JETTY-765 ensure stop mojo works for all execution phases
+ + JETTY-777 include util5 on the jetty debs
+ + JETTY-778 handle granular windows timer in lifecycle test
+ + JETTY-779 Fixed line feed in request log
+ + JETTY-782 Implement interval advice for BayeuxClient
+ + JETTY-783 Update jetty self-signed certificate
+ + JETTY-784 TerracottaSessionManager leaks sessions scavenged in other nodes
+ + JETTY-787 Handle MSIE7 mixed encoding
+ + JETTY-788 Fix jotm for new scoped jndi
+ + JETTY-790 WaitingContinuations can change mutex if not pending
+ + JETTY-791 Ensure jdk1.4 compatibility for jetty-6
+ + JETTY-792 TerracottaSessionManager does not unlock new session with
+   requested id
+ + JETTY-793 Fixed DataCache millisecond rounding
+
+jetty-6.1.12 - 04 November 2008
+ + JETTY-731 Completed DeliverListener for cometd
+ + JETTY-772 Increased default threadpool size to 250
+ + JETTY-774 Cached text/json content type
+ + JETTY-775 fix port of openspaces to jetty-6
+
+jetty-7.0.0.pre5 - 30 October 2008
+ + JETTY-766 Fix npe
+ + JETTY-767 Fixed SSL Client no progress handshake bug
+ + JETTY-768 Remove EnvEntry overloaded constructors
+ + JETTY-769 jquery example error
+ + JETTY-771 Ensure NamingEntryUtil is jdk1.4 compliant
+ + JETTY-772 Increased default threadpool size to 250
+
+jetty-6.1.12.rc5 - 30 October 2008
+ + JETTY-703 maxStopTimeMs added to QueuedThreadPool
+ + JETTY-762 improved QueuedThreadPool idle death handling
+ + JETTY-763 Fixed AJP13 constructor
+ + JETTY-766 Ensure SystemProperties set early on jetty-maven-plugin
+ + JETTY-767 Fixed SSL Client no progress handshake bug
+ + JETTY-768 Remove EnvEntry overloaded constructors
+ + JETTY-771 Ensure NamingEntryUtil jdk1.4 compliant
+
+jetty-7.0.0.pre4 - 28 October 2008
+ + JETTY-241 Support for web application overlays in rapid application
+   development (jetty:run)
+ + JETTY-319 improved passing of exception when webapp unavailable
+ + JETTY-331 SecureRandom hangs on systems with low entropy (connectors slow to
+   start)
+ + JETTY-591 No server classes for jetty-web.xml
+ + JETTY-604 AbstractSession.setSessionURL
+ + JETTY-670 $JETTY_HOME/bin/jetty.sh not worked in Solaris, because of
+   /usr/bin/which has no error-code
+ + JETTY-676 ResourceHandler doesn't support HTTP HEAD requests
+ + JETTY-677 GWT serialization issue
+ + JETTY-680 Can't configure the ResourceCollection with maven
+ + JETTY-681 JETTY-692 MultiPartFilter is slow for file uploads
+ + JETTY-682 Added listeners and queue methods to cometd
+ + JETTY-686 LifeCycle.Listener
+ + JETTY-687 Issue with servlet-mapping in dynamic servlet invoker
+ + JETTY-688 Cookie causes NumberFormatException
+ + JETTY-689 processing of non-servlet related annotations
+ + JETTY-690 Updated XBean dependencies to XBean version 3.4.3 and Spring
+   2.0.5.
+ + JETTY-696 jetty.sh restart not working
+ + JETTY-698 org.eclipse.resource.JarResource.extract does not close
+   JarInputStream jin
+ + JETTY-699 Optimized cometd sending of 1 message to many many clients
+ + JETTY-700 unit test for unread request data
+ + JETTY-703 maxStopTimeMs added to QueuedThreadPool
+ + JETTY-708 allow 3 scopes for jndi resources: jvm, server or webapp
+ + JETTY-709 Jetty plugin's WebAppConfig configured properties gets overridden
+   by AbstractJettyRunMojo even when already set
+ + JETTY-710 Worked around poor implementation of File.toURL()
+ + JETTY-711 DataSourceUserRealm implementation
+ + JETTY-712 HttpClient does not handle request complete after response
+   complete
+ + JETTY-715 AJP Key size as Integer
+ + JETTY-716 Fixed NPE on empty cometd message
+ + JETTY-718 during ssl unwrap, return true if some bytes were read, even if
+   underflow
+ + JETTY-720 fix HttpExchange.waitForStatus
+ + JETTY-721 Support wildcard in VirtualHosts configuration
+ + JETTY-723 jetty.sh does not check if TMP already is set
+ + JETTY-724 better handle EBCDIC default JVM encoding
+ + JETTY-728 Improve Terracotta integration and performances
+ + JETTY-730 Set SAX parse features to defaults
+ + JETTY-731 DeliverListener for cometd
+ + JETTY-732 Case Sensitive Basic Authentication Response Header
+   Implementations
+ + JETTY-733 Expose ssl connectors with xbean
+ + JETTY-735 Wrong default jndi name on DataSourceUserRealm
+ + JETTY-736 Client Specific cometd advice
+ + JETTY-737 refactored jetty.jar into jetty, xml, security, ssl, webapp and
+   deploy jars
+ + JETTY-738 If jetty.sh finds a pid file is does not check to see if a process
+   with that pid is still running
+ + JETTY-739 Race in QueuedThreadPool
+ + JETTY-741 HttpClient connects slowly due to reverse address lookup by
+   InetAddress.getHostName()
+ + JETTY-742 Private messages in cometd chat demo
+ + JETTY-747 Handle HttpClient exceptions better
+ + JETTY-755 Optimized HttpParser and buffers for few busy connections
+ + JETTY-757 Unhide JAAS classes
+ + JETTY-758 Update JSP to glassfish tag SJSAS-9_1_1-B51-18_Sept_2008
+ + JETTY-759 Fixed JSON small negative real numbers
+ + JETTY-760 Handle wildcard VirtualHost and normalize hostname in
+   ContextHandlerCollection
+ + JETTY-762 improved QueuedThreadPool idle death handling
+ + JETTY-763 Fixed AJP13 constructor
+ + JETTY-766 Ensure SystemProperties set early on jetty-maven-plugin
+
+jetty-6.1.12.rc4 - 21 October 2008
+ + JETTY-319 improved passing of exception when webapp unavailable
+ + JETTY-729 Backport Terracotta integration to Jetty6.1 branch
+ + JETTY-744 Backport of JETTY-741: HttpClient connects slowly due to reverse
+   address lookup by InetAddress.getHostName()
+ + JETTY-747 Handle exceptions better in HttpClient
+ + JETTY-755 Optimized HttpParser and buffers for few busy connections
+ + JETTY-758 Update JSP 2.1 to glassfish tag SJSAS-9_1_1-B51-18_Sept_2008
+ + JETTY-759 Fixed JSON small negative real numbers
+ + JETTY-760 Handle wildcard VirtualHost and normalize hostname in
+   ContextHandlerCollection
+
+jetty-6.1.12.rc3 - 10 October 2008
+ + JETTY-241 Support for web application overlays in rapid application
+   development (jetty:run)
+ + JETTY-686 LifeCycle.Listener
+ + JETTY-715 AJP key size
+ + JETTY-716 NPE for empty cometd message
+ + JETTY-718 during ssl unwrap, return true if some bytes were read, even if
+   underflow
+ + JETTY-720 fix HttpExchange.waitForStatus
+ + JETTY-721 Support wildcard in VirtualHosts configuration
+ + JETTY-722 jndi related threadlocal not cleared after deploying webapp
+ + JETTY-723 jetty.sh does not check if TMP already is set
+ + JETTY-725 port JETTY-708 (jndi scoping) to jetty-6
+ + JETTY-730 set SAX parser features to defaults
+ + JETTY-731 DeliverListener for cometd
+ + JETTY-732 Case Sensitive Basic Authentication Response Header
+   Implementations
+ + JETTY-736 Client Specific cometd advice
+ + JETTY-738 If jetty.sh finds a pid file is does not check to see if a process
+   with that pid is still running
+ + JETTY-739 Race in QueuedThreadPool
+ + JETTY-742 Private messages in cometd chat demo
+
+jetty-6.1.12rc2 - 12 September 2008
+ + JETTY-282 Support manually-triggered reloading
+ + JETTY-331 SecureRandom hangs on systems with low entropy (connectors slow to
+   startup)
+ + JETTY-591 No server classes for jetty-web.xml
+ + JETTY-670 $JETTY_HOME/bin/jetty.sh not worked in Solaris, because of
+   /usr/bin/which has no error-code
+ + JETTY-671 Configure DTD does not allow <Property> children
+ + JETTY-672 Utf8StringBuffer doesn't properly handle null characters (char
+   with byte value 0)
+ + JETTY-676 ResourceHandler doesn't support HTTP HEAD requests
+ + JETTY-677 GWT serialization issue
+ + JETTY-680 Can't configure the ResourceCollection with maven
+ + JETTY-681 JETTY-692 MultiPartFilter is slow for file uploads
+ + JETTY-682 Added listeners and queue methods to cometd
+ + JETTY-683 ResourceCollection works for jsp files but does not work for
+   static resources under DefaultServlet
+ + JETTY-687 Issue with servlet-mapping in dynamic servlet invoker
+ + JETTY-688 Cookie causes NumberFormatException
+ + JETTY-696 ./jetty.sh restart not working
+ + JETTY-698 org.eclipse.resource.JarResource.extract does not close
+   JarInputStream jin
+ + JETTY-699 Optimize cometd sending of 1 message to many many clients
+ + JETTY-709 Jetty plugin's WebAppConfig configured properties gets overridden
+   by AbstractJettyRunMojo even when already set
+ + JETTY-710 Worked around poor implementation of File.toURL()
+ + JETTY-712 HttpClient does not handle request complete after response
+   complete
+
+jetty-7.0.0pre3 - 06 August 2008
+ + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
+ + JETTY-30 Externalize servlet-api to own project
+ + JETTY-182 Support setting explicit system classpath for jasper
+   Jsr199JavaCompiler
+ + JETTY-319 Get unavailable exception and added startWithUnavailable option
+ + JETTY-381 JETTY-622 Multiple Web Application Source Directory
+ + JETTY-442 Accessors for mimeType on ResourceHandler
+ + JETTY-502 forward of an include should hide include attributes
+ + JETTY-562 RewriteHandler support for virtual hosts
+ + JETTY-563 JETTY-482 OpenRemoteServiceServlet for GWT1.5M2+
+ + JETTY-564 Consider optionally importing org.apache.jasper.servlet
+ + JETTY-571 SelectChannelConnector throws Exception on close on Windows
+ + JETTY-608 Suspend/Resume/Complete request listeners
+ + JETTY-621 Improved LazyList javadoc
+ + JETTY-626 Null protect reading the dtd resource from classloader
+ + JETTY-628 Rewrite rule for rewriting scheme
+ + JETTY-629 Don't hold timeout lock during expiry call.
+ + JETTY-632 OSGi tags for Jetty client
+ + JETTY-633 Default form encoding 8859_1 rather than utf-8
+ + JETTY-635 Correctly merge request parameters when doing forward
+ + JETTY-636 Separate lifeycle of jsp build
+ + JETTY-637 empty date headers throw IllegalArgumentException
+ + JETTY-641 JDBC Realm purge cache problem
+ + JETTY-642 NPE in LdapLoginModule
+ + JETTY-644 LdapLoginModule uses proper filters when searching
+ + JETTY-645 Do not provide jetty-util to the webapps
+ + JETTY-646 Should set Cache-Control header when sending errors to avoid
+   caching
+ + JETTY-647 suspended POSTs with binary data do too many resumes
+ + JETTY-650 Parse "*" URI for HTTP OPTIONS request
+ + JETTY-651 Release resources during destroy
+ + JETTY-653 Upgrade jta api specs to more recent version
+ + JETTY-654 Allow Cometd Bayeux object to be JMX manageable
+ + JETTY-655 Support parsing application/x-www-form-urlencoded parameters via
+   http PUT
+ + JETTY-656 HttpClient defaults to async mode
+ + JETTY-659 ContentExchange and missing headers in HttpClient
+ + JETTY-663 AbstractDatabaseLoginModule handle not found UserInfo and userName
+ + JETTY-665 Support merging class directories
+ + JETTY-666 scanTargetPatterns override the values already being set by
+   scanTarget
+ + JETTY-667 HttpClient handles chunked content
+ + JETTY-669 Http methods other than GET and POST should not have error page
+   content
+ + JETTY-671 Configure DTD does not allow <Property> children
+ + JETTY-672 Utf8StringBuffer doesn't properly handle null characters (char
+   with byte value 0)
+ + JETTY-675 ServletContext.getRealPath("") returns null instead of returning
+   the root dir of the webapp
+
+jetty-6.1.12rc1 - 01 August 2008
+ + Upgrade jsp 2.1 to SJSAS-9_1_02-B04-11_Apr_2008
+ + JETTY-319 Get unavailable exception and added startWithUnavailable option
+ + JETTY-381 JETTY-622 Multiple Web Application Source Directory
+ + JETTY-442 Accessors for mimeType on ResourceHandler
+ + JETTY-502 forward of an include should hide include attributes
+ + JETTY-562 RewriteHandler support for virtual hosts
+ + JETTY-563 GWT OpenRemoteServiceServlet GWT1.5M2+
+ + JETTY-564 Consider optionally importing org.apache.jasper.servlet
+ + JETTY-571 SelectChannelConnector throws Exception on close on Windows
+ + JETTY-596 Proxy authorization support in HttpClient
+ + JETTY-599 handle buffers consistently handle invalid index for poke
+ + JETTY-603 Handle IPv6 in HttpURI
+ + JETTY-605 Added optional threadpool to BayeuxService
+ + JETTY-606 better writeTo impl for BIO
+ + JETTY-607 Add GigaSpaces session clustering
+ + JETTY-610 jetty.class.path not being interpreted
+ + JETTY-613 website module now generates site-component for jetty-site
+ + JETTY-614 scanner allocated hashmap on every scan
+ + JETTY-623 ServletContext.getServerInfo() non compliant
+ + JETTY-626 Null protect reading the dtd resource from classloader
+ + JETTY-628 Rewrite rule for rewriting scheme
+ + JETTY-629 Don't hold timeout lock during expiry call.
+ + JETTY-632 OSGi tags for Jetty client
+ + JETTY-633 Default form encoding 8859_1 rather than utf-8
+ + JETTY-635 Correctly merge request parameters when doing forward
+ + JETTY-637 empty date headers throw IllegalArgumentException
+ + JETTY-641 JDBC Realm purge cache problem
+ + JETTY-642 NPE in LdapLoginModule
+ + JETTY-644 LdapLoginModule uses proper filters when searching
+ + JETTY-646 Should set Cache-Control header when sending errors to avoid
+   caching
+ + JETTY-647 suspended POSTs with binary data do too many resumes
+ + JETTY-650 Parse "*" URI for HTTP OPTIONS request
+ + JETTY-651 Release resources during destroy
+ + JETTY-654 Allow Cometd Bayeux object to be JMX manageable
+ + JETTY-655 Support parsing application/x-www-form-urlencoded parameters via
+   http PUT
+ + JETTY-656 HttpClient defaults to async mode
+ + JETTY-657 Backport jetty-7 sslengine
+ + JETTY-658 backport latest HttpClient from jetty-7 to jetty-6
+ + JETTY-659 ContentExchange and missing headers in HttpClient
+ + JETTY-660 Backported QoSFilter
+ + JETTY-663 AbstractDatabaseLoginModule handle not found UserInfo and userName
+ + JETTY-665 Support merging class directories
+ + JETTY-666 scanTargetPatterns override the values already being set by
+   scanTarget
+ + JETTY-667 HttpClient handles chunked content
+ + JETTY-669 Http methods other than GET and POST should not have error page
+   content
+
+jetty-7.0.0pre2 - 30 June 2008
+ + JETTY-336 413 error for header buffer full
+ + JETTY-425 race in stopping SelectManager
+ + JETTY-568 Avoid freeing DirectBuffers. New locking NIO ResourceCache.
+ + JETTY-569 Stats for suspending requests
+ + JETTY-576 servlet dtds and xsds not being loaded locally
+ + JETTY-572 Unique cometd client ID
+ + JETTY-578 OSGI Bundle-RequiredExcutionEnvironment set to J2SE-1.5
+ + JETTY-579 OSGI resolved management and servlet.resources import error
+ + JETTY-580 Fixed SSL shutdown
+ + JETTY-581 ContextPath constructor
+ + JETTY-582 final ISO_8859_1
+ + JETTY-584 handle null contextPath
+ + JETTY-587 persist sessions to database
+ + JETTY-588 handle Retry in ServletException
+ + JETTY-589 Added Statistics Servlet
+ + JETTY-590 Digest auth domain for root context
+ + JETTY-592 expired timeout callback without synchronization
+ + JETTY-595 SessionHandler only deals with base request session
+ + JETTY-596 proxy support in HttpClient
+ + JETTY-598 Added more reliable cometd message flush option
+ + JETTY-599 handle buffers consistently handle invalid index for poke
+ + JETTY-603 Handle IPv6 in HttpURI
+ + JETTY-605 Added optional threadpool to BayeuxService
+ + JETTY-606 better writeTo impl for BIO
+ + JETTY-607 Add GigaSpaces session clustering
+ + JETTY-609 jetty-client improvements for http conversations
+ + JETTY-610 jetty.class.path not being interpreted
+ + JETTY-611 make general purpose jar scanning mechanism
+ + JETTY-612 scan for web.xml fragments
+ + JETTY-613 various distribution related changes
+ + JETTY-614 scanner allocates hashmap on every iteration
+ + JETTY-615 Replaced CDDL servlet.jar with Apache-2.0 licensed version
+ + JETTY-623 ServletContext.getServerInfo() non compliant
+
+jetty-6.1.11 - 06 June 2008
+ + JETTY-336 413 error for full header buffer
+ + JETTY-425 race in stopping SelectManager
+ + JETTY-580 Fixed SSL shutdown
+ + JETTY-581 ContextPath constructor
+ + JETTY-582 final ISO_8859_1
+ + JETTY-584 handle null contextPath
+ + JETTY-588 handle Retry in ServletException
+ + JETTY-590 Digest auth domain for root context
+ + JETTY-592 expired timeout callback without synchronization
+ + JETTY-595 SessionHandler only deals with base request session
+ + JETTY-596 Proxy support in HttpClient
+ + JETTY-598 Added more reliable cometd message flush option
+
+jetty-6.1.10 - 20 May 2008
+ + Use QueuedThreadPool as default
+ + JETTY-440 allow file name patterns for jsp compilation for jspc plugin
+ + JETTY-529 CNFE when deserializing Array from session resolved
+ + JETTY-537 JSON handles Locales
+ + JETTY-547 Shutdown SocketEndpoint output before close
+ + JETTY-550 Reading 0 bytes corrupts ServletInputStream
+ + JETTY-551 Upgraded to Wadi 2.0-M10
+ + JETTY-556 Encode all URI fragments
+ + JETTY-557 Allow ServletContext.setAttribute before start
+ + JETTY-558 optional handling of X-Forwarded-For/Host/Server
+ + JETTY-566 allow for non-blocking behavior in jetty maven plugin
+ + JETTY-572 unique cometd client ID
+ + JETTY-579 osgi fixes with management and servlet resources
+
+jetty-7.0.0pre1 - 03 May 2008
+ + Allow annotations example to be built regularly, copy to contexts-available
+ + Make annotations example consistent with servlet 3.0
+ + Refactor JNDI impl to simplify
+ + Improved suspend examples
+ + address osgi bundling issue relating to build resources
+ + JETTY-529 CNFE when deserializing Array from session resolved
+ + JETTY-558 optional handling of X-Forwarded-For/Host/Server
+ + JETTY-559 ignore unsupported shutdownOutput
+ + JETTY-566 allow for non-blocking behavior in jetty maven plugin
+ + JETTY-440 allow file name patterns for jsp compilation for jspc plugin
+
+jetty-7.0.0pre0 - 21 April 2008
+ + Jetty-6.1.8 Changes
+ + Refactor of Continuation towards servlet 3.0 proposal
+ + JETTY-282 Support manually-triggered reloading by maven plugin
+ + QueuedThreadPool default
+ + RetryRequest exception now extends ThreadDeath
+ + Added option to dispatch to suspended requests.
+ + Delay 100 continues until getInputStream
+ + HttpClient supports pipelined request
+ + BayeuxClient use a single connection for polling
+ + Make javax.servlet.jsp optional osgi import for jetty module
+ + Ensure Jotm tx mgr can be found in jetty-env.xml
+ + Renamed modules management and naming to jmx and jndi.
+ + JETTY-341 100-Continues sent only after getInputStream called.
+ + JETTY-386 backout fix and replaced with
+   ContextHandler.setCompactPath(boolean)
+ + JETTY-399 update OpenRemoteServiceServlet to gwt 1.4
+ + JETTY-467 allow URL rewriting to be disabled.
+ + JETTY-468 unique holder names for addServletWithMapping
+ + JETTY-471 LDAP JAAS Realm
+ + JETTY-474 Fixed case sensitivity issue with HttpFields
+ + JETTY-475 AJP connector in RPMs
+ + JETTY-486 Improved jetty.sh script
+ + JETTY-487 Handle empty chunked request
+ + JETTY-494 Client side session replication
+ + JETTY-519 HttpClient does not recycle closed connection.
+ + JETTY-522 Add build profile for macos for setuid
+ + JETTY-523 Default servlet uses ServletContext.getResource
+ + JETTY-524 Don't synchronize session event listener calls
+ + JETTY-525 Fixed decoding for long strings
+ + JETTY-526 Fixed MMBean fields on JMX MBeans
+ + JETTY-528 Factor our cookie parsing to CookieCutter
+ + JETTY-530 Improved JMX MBeanContainer lifecycle
+ + JETTY-531 Optional expires on MovedContextHandler
+ + JETTY-532 MBean properties for QueuedThreadPool
+ + JETTY-535 Fixed Bayeux server side client memory leak
+ + JETTY-537 JSON handles Locales
+ + JETTY-538 test harness fix for windows
+ + JETTY-540 Servlet-3.0 & java5 support (work in progress)
+ + JETTY-543 Atomic batch get and put of files.
+ + JETTY-545 Rewrite handler
+ + JETTY-546 Webapp runner. All in one jar to run a webapps
+ + JETTY-547 Shutdown SocketEndpoint output before close
+ + JETTY-550 Reading 0 bytes corrupts ServletInputStream
+ + JETTY-551 Wadi 2.0-M10
+ + JETTY-553 Fixed customize override
+ + JETTY-556 Encode all URI fragments
+ + JETTY-557 Allow ServletContext.setAttribute before start
+ + JETTY-560 Allow decoupling of jndi names in web.xml
+
+jetty-6.1.9 - 26 March 2008
+ + Make javax.servlet.jsp optional osgi import for jetty module
+ + Ensure Jotm tx mgr can be found in jetty-env.xml
+ + JETTY-399 update OpenRemoteServiceServlet to gwt 1.4
+ + JETTY-471 LDAP JAAS Realm
+ + JETTY-475 AJP connector in RPMs
+ + JETTY-482 update to JETTY-399
+ + JETTY-519 HttpClient does not recycle closed connection.
+ + JETTY-522 Add build profile for macos for setuid
+ + JETTY-525 Fixed decoding for long strings
+ + JETTY-526 Fixed MMBean fields on JMX MBeans
+ + JETTY-532 MBean properties for QueuedThreadPool
+ + JETTY-535 Fixed Bayeux server side client memory leak
+ + JETTY-538 test harness fix for windows
+ + JETTY-541 Cometd per client timeouts
+
+jetty-6.1.8 - 28 February 2008
+ + Added QueuedThreadPool
+ + Optimized QuotedStringTokenizer.quote()
+ + further Optimizations and improvements of Cometd
+ + Optimizations and improvements of Cometd, more pooled objects
+ + Improved Cometd timeout handling
+ + Added BayeuxService
+ + Cookie support in BayeuxClient
+ + Improved Bayeux API
+ + add removeHandler(Handler) method to HandlerContainer interface
+ + Added JSON.Convertor and non static JSON instances
+ + Long cache for JSON
+ + Fixed JSON negative numbers
+ + JSON unquotes /
+ + Add "mvn jetty:stop"
+ + allow sessions to be periodically persisted to disk
+ + grizzly fixed for posts
+ + Remove duplicate commons-logging jars and include sslengine in jboss sar
+ + Allow code ranges on ErrorPageErrorHandler
+ + AJP handles bad mod_jk methods
+ + JETTY-350 log ssl errors on SslSocketConnector
+ + JETTY-417 JETTY_LOGS environment variable not queried by jetty.sh
+ + JETTY-433 ContextDeployer constructor fails unnecessarily when using a
+   security manager if jetty.home not set
+ + JETTY-434 ContextDeployer scanning of sub-directories should be optional
+ + JETTY-481 Handle empty Bayeux response
+ + JETTY-489 Improve doco on the jetty.port property for plugin
+ + JETTY-490 Fixed JSONEnumConvertor
+ + JETTY-491 opendocument mime types
+ + JETTY-492 Null pointer in HashSSORealm
+ + JETTY-493 JSON handles BigDecimals
+ + JETTY-498 Improved cookie parsing
+ + JETTY-507 Fixed encoding from JETTY-388 and test case
+ + JETTY-508 Extensible cometd handlers
+ + JETTY-509 Fixed JSONP transport for changing callback names
+ + JETTY-511 jetty.sh mishandled JETTY_HOME when launched from a relative path
+ + JETTY-512 add slf4j as optional to manifest
+ + JETTY-513 Terracotta session replication does not work when the initial page
+   on each server does not set any attributes
+ + JETTY-515 Timer is missing scavenging Task in HashSessionManager
+
+jetty-6.1.7 - 22 December 2007
+ + Added BayeuxService
+ + Added JSON.Convertor and non static JSON instances
+ + Add "mvn jetty:stop"
+ + allow sessions to be periodically persisted to disk
+ + Cookie support in BayeuxClient
+ + grizzly fixed for posts
+ + jetty-6.1 branch created from 6.1.6 and r593 of jetty-contrib trunk
+ + Optimizations and improvements of Cometd, more pooled objects
+ + Update java5 patch
+ + JETTY-386 CERT-553235 backout fix and replaced with
+   ContextHandler.setCompactPath(boolean)
+ + JETTY-467 allow URL rewriting to be disabled.
+ + JETTY-468 unique holder names for addServletWithMapping
+ + JETTY-474 Fixed case sensitivity issue with HttpFields
+ + JETTY-486 Improved jetty.sh script
+ + JETTY-487 Handle empty chunked request
+
+jetty-6.1.6 - 18 November 2007
+ + rudimentary debian packaging
+ + updated grizzly connector to 1.6.1
+ + JETTY-455 Optional cometd id
+ + JETTY-459 Unable to deploy from Eclipse into the root context
+ + JETTY-461 fixed cometd unknown channel
+ + JETTY-464 typo in ErrorHandler
+ + JETTY-465 System.exit() in constructor exception for MultiPartOutputStream
+
+jetty-6.1.6rc1 - 05 November 2007
+ + Upgrade jsp 2.1 to SJSAS-9_1-B58G-FCS-08_Sept_2007
+ + Housekeeping on poms
+ + CERT VU#38616 handle single quotes in cookie names.
+ + Improved JSON parsing from Readers
+ + Moved some impl classes from jsp-api-2.1 to jsp-2.1
+ + Added configuration file for capturing stderr and stdout
+ + Updated for dojo 1.0(rc) cometd
+ + Give bayeux timer name
+ + Give Terracotta session scavenger a name
+ + Jetty Eclipse Plugin 1.0.1: force copy of context file on redeploy
+ + JETTY-388 Handle utf-16 and other multibyte non-utf-8 form content.
+ + JETTY-409 String params that denote files changed to File
+ + JETTY-438 handle trailing . in vhosts
+ + JETTY-439 Fixed 100 continues clash with Connection:close
+ + JETTY-451 Concurrent modification of session during invalidate
+ + JETTY-443 windows bug causes Acceptor thread to die
+ + JETTY-445 removed test code
+ + JETTY-448 added setReuseAddress on AbstractConnector
+ + JETTY-450 Bad request for response sent to server
+ + JETTY-452 CERT VU#237888 Dump Servlet - prevent cross site scripting
+ + JETTY-453 updated Wadi to 2.0-M7
+ + JETTY-454 handle exceptions with themselves as root cause
+ + JETTY-456 allow null keystore for osX
+ + JETTY-457 AJP certificate chains
+
+jetty-6.1.6rc0 - 03 October 2007
+ + Added jetty.lib system property to start.config
+ + AJP13 Fix on chunked post
+ + Fix cached header optimization for extra characters
+ + SetUID option to support setgid
+ + Make mx4j used only if runtime uses jdk<1.5
+ + Moved Grizzly to contrib
+ + Give deployment file Scanner threads a unique name
+ + Fix Host header for async client
+ + Fix typo in async client onResponsetHeader method name
+ + Tweak OSGi manifests to remove unneeded imports
+ + Allow scan interval to be set after Scanner started
+ + Add jetty.host system property
+ + Allow properties files on the XmlConfiguration command line.
+ + Prevent infinite loop on stopping with temp dir
+ + Ensure session is completed only when leaving context.
+ + Update terracotta to 2.4.1 and exclude ssl classes
+ + Update jasper2.1 to tag SJSAS-9_1-B58C-FCS-22_Aug_2007
+ + Removal of unneeded dependencies from management, maven-plugin, naming &
+   plus poms
+ + Adding setUsername,setGroupname to setuid and mavenizing native build
+ + UTF-8 for bayeux client
+ + CVE-2007-5615 Added protection for response splitting with bad headers.
+ + Cached user agents strings in the /org/mortbay/jetty/useragents resource
+ + Make default time format for RequestLog match NCSA default
+ + Use terracotta repo for build; make jetty a terracotta module
+ + Fix patch for java5 to include cometd module
+ + Added ConcatServlet to combine javascript and css
+ + Add ability to persist sessions with HashSessionManager
+ + Avoid FULL exception in window between blockForOutput and remote close
+ + Added JPackage RPM support
+ + Added JSON.Convertable
+ + Updated README, test index.html file and jetty-plus.xml file
+ + JETTY-259 SystemRoot set for windows CGI
+ + JETTY-311 avoid json keywords
+ + JETTY-376 allow anything but CRLF in reason string
+ + JETTY-398 Allow same WADI Dispatcher to be used across multiple web-app
+   contexts
+ + JETTY-400 consume CGI stderr
+ + JETTY-402 keep HashUserRealm in sync with file
+ + JETTY-403 Allow long content length for range requests
+ + JETTY-404 WebAppDeployer sometimes deploys duplicate webapp
+ + JETTY-405 Default date formate for reqest log
+ + JETTY-407 AJP handles unknown content length
+ + JETTY-413 Make rolloveroutputstream timer daemon
+ + JETTY-422 Allow <Property> values to be null in config files
+ + JETTY-423 Ensure javax.servlet.forward parameters are latched on first
+   forward
+ + JETTY-425 Handle duplicate stop calls better
+ + JETTY-430 improved cometd logging
+ + JETTY-431 HttpClient soTimeout
+
+jetty-6.1.5 - 19 July 2007
+ + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
+ + Fixed GzipFilter for dispatchers
+ + Fixed reset of reason
+ + JETTY-392 updated LikeJettyXml example
+
+jetty-6.1.5rc0 - 15 July 0200
+ + update terracotta session clustering to terracotta 2.4
+ + SetUID option to only open connectors before setUID.
+ + Protect SslSelectChannelConnector from exceptions during close
+ + Improved Request log configuration options
+ + Added GzipFilter and UserAgentFilter
+ + make OSGi manifests for jetty jars
+ + update terracotta configs for tc 2.4 stable1
+ + remove call to open connectors in jetty.xml
+ + update links on website
+ + make jetty plus example webapps use ContextDeployer
+ + Dispatch SslEngine expiry (non atomic)
+ + Make SLF4JLog impl public, add mbean descriptors
+ + SPR-3682 - dont hide forward attr in include.
+ + Upgrade to Jasper 2.1 tag SJSAS-9_1-B50G-BETA3-27_June_2007
+ + JETTY-253 Improved graceful shutdown
+ + JETTY-373 Stop all dependent lifecycles
+ + JETTY-374 HttpTesters handles large requests/responses
+ + JETTY-375 IllegalStateException when committed.
+ + JETTY-376 allow spaces in reason string
+ + JETTY-377 allow sessions to be wrapped with
+   AbstractSesssionManager.SessionIf
+ + JETTY-378 handle JVMs with non ISO/UTF default encodings
+ + JETTY-380 handle pipelines of more than 4 requests!
+ + JETTY-385 EncodeURL for new sessions from dispatch
+ + JETTY-386 Allow // in file resources
+
+jetty-6.1.4 - 15 June 2007
+ + fixed early open() call in NIO connectors
+ + JETTY-370 ensure maxIdleTime<=0 means connections never expire
+ + JETTY-371 Fixed chunked HEAD response
+ + JETTY-372 make test for cookie caching more rigorous
+
+jetty-6.1.4rc1 - 10 June 2007
+ + Work around IBM JVM socket close issue
+ + moved documentation for jetty and jspc maven plugins to wiki
+ + async client improvements
+ + fixed handling of large streamed files
+ + Fixed synchronization conflict SslSelectChannel and SelectChannel
+ + Optional static content cache
+ + JETTY-310 better exception when no filter file for cometd servlet
+ + JETTY-323 handle htaccess without a user realm
+ + JETTY-346 add wildcard support to extra scan targets for maven plugin
+ + JETTY-355 extensible SslSelectChannelConnector
+ + JETTY-357 cleaned up ssl buffering
+ + JETTY-360 allow connectors, userRealms to be added from a <jettyConfig> for
+   maven plugin
+ + JETTY-361 prevent url encoding of dir listings for non-link text
+ + JETTY-362 More object locks
+ + JETTY-365 make needClientAuth work on SslSelectChannelConnector
+ + JETTY-366 JETTY-368 Improved bayeux disconnect
+
+jetty-6.1.4rc0 - 01 June 2007
+ + Reorganized import of contrib modules
+ + Unified JMX configuration
+ + Updated slf4j version to 1.3.1
+ + Updated junit to 3.8.2
+ + Allow XmlConfiguration properties to be configured
+ + Add (commented out) jspc precompile to test-webapp
+ + Add slf4j-api for upgraded version
+ + Change scope of fields for Session
+ + Add ability to run cometd webapps to maven plugin
+ + Delay ssl handshake until after dispatch in sslSocketConnector
+ + Set so_timeout during ssl handshake as an option on SslSocketConnector
+ + Optional send Date header. Server.setSendDateHeader(boolean)
+ + update etc/jetty-ssl.xml with new handshake timeout setting
+ + fixed JSP close handling
+ + improved date header handling
+ + fixed waiting continuation reset
+ + JETTY-257 fixed comet cross domain
+ + JETTY-309 fix applied to sslEngine
+ + JETTY-317 rollback inclusion of cometd jar for maven plugin
+ + JETTY-318 Prevent meta channels being created
+ + JETTY-330 Allow dependencies with scope provided for jspc plugin
+ + JETTY-335 SslEngine overflow fix
+ + JETTY-337 deprecated get/setCipherSuites and added
+   get/setExcludeCipherSuites
+ + JETTY-338 protect isMoreInBuffer from destroy
+ + JETTY-339 MultiPartFiler deletes temp files on IOException
+ + JETTY-340 FormAuthentication works with null response
+ + JETTY-344 gready fill in ByteArrayBuffer.readFrom
+ + JETTY-345 fixed lost content with blocked NIO.
+ + JETTY-347 Fixed type util init
+ + JETTY-352 Object locks
+
+jetty-6.1.3 - 04 May 2007
+ + Handle CRLF for content in header optimization
+ + JETTY-309 don't clear writable status until dispatch
+ + JETTY-315 suppressed warning
+ + JETTY-322 AJP13 cping and keep alive
+
+jetty-6.1.2 - 01 May 2007
+ + Improved unavailabile handling
+ + sendError resets output state
+ + Fixed session invalidation error in WadiSessionManager
+ + Updated Wadi to version 2.0-M3
+ + Added static member definition in WadiSessionManager
+ + JETTY-322 fix ajp cpong response and close handling
+ + JETTY-324 fix ant plugin
+ + JETTY-328 updated jboss
+
+jetty-6.1.2rc5 - 24 April 2007
+ + set default keystore for SslSocketConnector
+ + removed some compile warnings
+ + Allow jsp-file to be / or /*
+ + JETTY-305 delayed connection destroy
+ + JETTY-309 handle close in multivalue connection fields.
+ + JETTY-314 fix for possible NPE in Request.isRequestedSessionIdValid
+
+jetty-6.1.2rc4 - 19 April 2007
+ + JETTY-294 Fixed authentication reset
+ + JETTY-299 handle win32 paths for object naming
+ + JETTY-300 removed synchronized on dispatch
+ + JETTY-302 correctly parse quoted content encodings
+ + JETTY-303 fixed dual reset of generator
+ + JETTY-304 Fixed authentication reset
+
+jetty-6.1.2rc3 - 16 April 2007
+ + Improved performance and exclusions for TLD scanning
+ + MBean properties assume writeable unless marked RO
+ + refactor of SessionManager and SessionIdManager for clustering
+ + Improvements to allow simple setting of Cache-Control headers
+ + AJP redirects https requests correctly
+ + Fixed writes of unencoded char arrays.
+ + JETTY-283 Parse 206 and 304 responses in client
+ + JETTY-285 enable jndi for mvn jetty:run-war and jetty:run-exploded
+ + JETTY-289 fixed javax.net.ssl.SSLException on binary file upload
+ + JETTY-292 Fixed error page handler error pages
+ + JETTY-293 fixed NPE on fast init
+ + JETTY-294 Response.reset() resets headers as well as content
+ + JETTY-295 Optional support of authenticated welcome files
+ + JETTY-296 Close direct content inputstreams
+ + JETTY-297 Recreate tmp dir on stop/start
+ + JETTY-298 Names in JMX ObjectNames for context, servlets and filters
+
+jetty-6.1.2rc2 - 27 March 2007
+ + Enable the SharedStoreContextualiser for the WadiSessionManager(Database
+   store for clustering)
+ + AJP13 CPING request and CPONG response implemented
+ + AJP13 Shutdown Request from peer implemented
+ + AJP13 remoteUser, contextPath, servletPath requests implemented
+ + Change some JNDI logging to debug level instead of info
+ + Update jasper to glassfish tag SJSAS-9_1-B39-RC-14_Mar_2007
+ + Optimized multi threaded init on startup servlets
+ + Removed unneeded specialized TagLibConfiguration class from maven plugin
+ + Refactor Scanner to increase code reuse with maven/ant plugins
+ + Added RestFilter for PUT and DELETE from Aleksi Kallio
+ + Make annotations work for maven plugin
+ + JETTY-125 maven plugin: ensure test dependencies on classpath for
+   <useTestClasspath>
+ + JETTY-246 path encode cookies rather than quote
+ + JETTY-254 prevent close of jar entry by bad JVMs
+ + JETTY-256 fixed isResumed and work around JVM bug
+ + JETTY-258 duplicate log message in ServletHandler
+ + JETTY-260 Close connector before stop
+ + JETTY-262 Allow acceptor thread priority to be adjusted
+ + JETTY-263 Added implementation for authorizationType Packets
+ + JETTY-265 Only quote cookie values if needed
+ + JETTY-266 Fix deadlock with shutdown
+ + JETTY-271 ResourceHandler uses resource for MimeType mapping
+ + JETTY-272 Activate and Passivate events for sessions
+ + JETTY-274 Improve flushing at end of request for blocking
+ + JETTY-276 Partial fix for reset/close race
+ + JETTY-277 Improved ContextHandlerCollection
+ + JETTY-278 Session invalidation delay until no requests
+ + JETTY-280 Fixed deadlock with two flushing threads
+ + JETTY-284 Fixed stop connector race
+ + JETTY-286 isIntegral and isConfidential methods overridden in
+   SslSelectChannelConnector
+
+jetty-6.1.2rc1 - 08 March 2007
+ + TagLibConfiguration uses resource input stream
+ + Improved handling of early close in AJP
+ + add ajp connector jar to jetty-jboss sar
+ + Improved Context setters for wadi support
+ + fix Dump servlet to handle primitive array types
+ + handle comma separated values for the Connection: header
+ + Added option to allow null pathInfo within context
+ + BoundedThreadPool queues rather than blocks excess jobs.
+ + Support null pathInfo option for webservices deployed to jetty/jboss
+ + Workaround to call SecurityAssocation.clear() for jboss webservices calls to
+   ejbs
+ + Ensure jetty/jboss uses servlet-spec classloading order
+ + call preDestroy() after servlet/filter destroy()
+ + Fix constructor for Constraint to detect wildcard role
+ + Added support for lowResourcesIdleTime to SelectChannelConnector
+ + JETTY-157 make CGI handle binary data
+ + JETTY-175 JDBCUserRealm use getInt instead of getObject
+ + JETTY-188 Use timer for session scavaging
+ + JETTY-235 default realm name
+ + JETTY-242 fix race condition with scavenging sessions when stopping
+ + JETTY-243 FULL
+ + JETTY-244 Fixed UTF-8 buffer overflow
+ + JETTY-245 Client API improvements
+ + JETTY-246 spaces in cookies
+ + JETTY-248 setContentLength after content written
+ + JETTY-250 protect attribute enumerations from modification
+ + JETTY-252 Fixed stats handling of close connection
+ + JETTY-254 prevent close of jar file by bad JVMs
+
+jetty-6.1.2rc0 - 15 February 2007
+ + JETTY-223 Fix disassociate of UserPrincipal on dispatches
+ + JETTY-226 Fixed SSLEngine close issue
+ + JETTY-232 Fixed use of override web.xml
+ + JETTY-236 Buffer leak
+ + JETTY-237 AJPParser Buffer Data Handling
+ + JETTY-238 prevent form truncation
+ + Patches from sybase for ClientCertAuthenticator
+ + Coma separated cookies
+ + Cometd timeout clients
+
+jetty-6.1.2pre1 - 05 February 2007
+ + JETTY-224 run build up to process-test before invoking jetty:run
+ + Added error handling for incorrect keystore/truststore password in
+   SslSelectChannelConnector
+ + fixed bug with virtual host handling in ContextHandlerCollection
+ + added win32service to standard build
+ + refactored cometd to be continuation independent
+ + allow ResourceHandler to use resource base from an enclosing ContextHandler
+
+jetty-6.1.2pre0 - 01 February 2007
+ + Fixed 1.4 method in jetty plus
+ + Fixed generation of errors during jsp compilation for jsp-2.1
+ + Added cometd jsonp transport from aabeling
+ + Added terracotta cluster support for cometd
+ + JETTY-213 request.isUserInRole(String) fixed
+ + JETTY-215 exclude more transitive dependencies from tomcat jars for jsp-2.0
+ + JETTY-216 handle AJP packet fragmentation
+ + JETTY-218 handle AJP ssl key size and integer
+ + JETTY-219 fixed trailing encoded chars in cookies
+ + JETTY-220 fixed AJP content
+ + JETTY-222 fix problem parsing faces-config.xml
+ + add support for Annotations in servlet, filter and listener sources
+ + improved writer buffering
+ + moved JSON parser to util to support reuse
+ + handle virtual hosts in ContextHandlerCollection
+ + enable SslSelectChannelConnector to modify the SslEngine's client
+   authentication settings
+
+jetty-6.1.1 - 15 January 2007
+
+jetty-6.1.1rc1 - 12 January 2007
+ + Use timers for Rollover logs and scanner
+ + JETTY-210 Build jsp-api-2.0 for java 1.4
+
+jetty-6.1.1rc0 - 10 January 2007
+ + Fixed unpacking WAR
+ + extras/win32service download only if no JavaServiceWrapper exist
+ + MultiPartFilter deleteFiles option
+ + CGI servlet fails without exception
+ + JETTY-209 Added ServletTester.createSocketConnector
+ + JETTY-210 Build servlet-api-2.5 for java 1.4
+ + JETTY-211 fixed jboss build
+ + ensure response headers on AjaxFilter messsages turn off caching
+ + start webapps on deployment with jboss, use isDistributed() method from
+   WebAppContext
+ + simplified chat demo
+
+jetty-6.1.0 - 09 January 2007
+ + Fixed unpacking WAR
+
+jetty-6.1.0 - 05 January 2007
+ + Improved config of java5 threadpool
+ + Protect context deployer from Errors
+ + Added WebAppContext.setCopyWebDir to avoid JVM jar caching issues.
+ + GERONIMO-2677 refactor of session id handling for clustering
+ + ServletTester sets content length
+ + Added extras/win32service
+ + JETTY-206 fixed AJP getServerPort and getRemotePort
+
+jetty-6.1.0rc3 - 02 January 2007
+ + JETTY-195 fixed ajp ssl_cert handling
+ + JETTY-197 fixed getRemoteHost
+ + JETTY-203 initialize ServletHandler if no Context instance
+ + JETTY-204 setuid fix
+ + setLocale does not use default content type
+ + Use standard releases of servlet and jsp APIs.
+ + implement resource injection and lifecycle callbacks declared in web.xml
+ + extras/servlet-tester
+
+jetty-6.1.0rc2 - 20 December 2006
+ + AJP13Parser, throw IllegalStateException on unimplemented AJP13 Requests
+ + ContextHandlerCollection is noop with no handlers
+ + ensure servlets initialized if only using ServletHandler
+ + fixed Jetty-197 AJP13 getRemoteHost()
+ + Refactored AbstractSessionManager for ehcache
+ + ensure classpath passed to jspc contains file paths not urls
+ + JETTY-194 doubles slashes are significant in URIs
+ + JETTY-167 cometd refactor
+ + remove code to remove SecurityHandler if no constraints present
+ + JETTY-201 make run-as work for both web container and ejb container in jboss
+ + ensure com.sun.el.Messages.properties included in jsp-2.1 jar
+
+jetty-6.1.0rc1 - 14 December 2006
+ + simplified idle timeout handling
+ + JETTY-193 MailSessionReference without authentication
+ + JETTY-199 newClassPathResource
+ + ensure unique name for ServletHolder instances
+ + added cache session manager(pre-alpha)
+
+jetty-6.1.0rc0 - 08 December 2006
+ + JETTY-181 Allow injection of a java:comp Context
+ + JETTY-182 Optionally set JSP classpath initparameter
+ + Dispatcher does not protect javax.servlet attributes
+ + DefaultHandler links virtual hosts.
+ + Fixed cachesize on invalidate
+ + Optimization of writers
+ + ServletHandler allows non REQUEST exceptions to propogate
+ + TCK fixes from Sybase:
+ + Handle request content encodings
+ + forward query attribute fix
+ + session attribute listener
+ + Servlet role ref
+ + flush if content-length written
+ + 403 for BASIC authorization failure
+ + null for unknown named dispatches
+ + JETTY-184 cometd connect non blocking
+ + Support for RFC2518 102-processing response
+ + JETTY-123 fix improved
+ + Added org.mortbay.thread.concurrent.ThreadPool
+ + Added extras/gwt
+ + Fixed idle timeout
+ + JETTY-189 ProxyConnection
+ + Added spring ejb3 demo example
+ + update jasper to glassfish SJSAS-9_1-B27-EA-07_Dec_2006
+ + JETTY-185 tmp filename generation
+
+jetty-6.1.0pre3 - 22 November 2006
+ + fixed NIO endpoint flush. Avoid duplicate sends
+ + CVE-2006-6969 Upgraded session ID generation to use SecureRandom
+ + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
+ + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ + JETTY-180 XBean support for context deploy
+ + JETTY-154 Cookies are double quotes only
+ + Expose isResumed on Continuations
+ + Refactored AJP generator
+
+jetty-6.0.2 - 22 November 2006
+ + Moved all modules updates from 6.1pre2 to 6.0
+ + Added concept of bufferred endpoint
+ + Added conversion Object -> ObjectName for the result of method calls made on
+   MBeans
+ + Added DataFilter configuration to cometd
+ + added examples/test-jaas-webapp
+ + Added extraClassPath to WebAppContext
+ + Added hierarchical destroy of mbeans
+ + Added ID constructor to AbstractSessionManager.Session
+ + added isStopped() in LifeCycle and AbstractLifeCycle
+ + Added override descriptor for deployment of RO webapps
+ + add <Property> replacement in jetty xml config files
+ + alternate optimizations of writer (use -Dbuffer.writers=true)
+ + Allow session cookie to be refreshed
+ + Apply queryEncoding to getQueryString
+ + CGI example in test webapp
+ + change examples/test-jndi-webapp so it can be regularly built
+ + Default soLinger is -1 (disabled)
+ + ensure "" returned for ServletContext.getContextPath() for root context
+ + ensure sessions nulled out on request recycle; ensure session null after
+   invalidate
+ + ensure setContextPath() works when invoked from jetty-web.xml
+ + fixed NIO endpoint flush. Avoid duplicate sends
+ + Fixed NPE in bio.SocketEndPoint.getRemoteAddr()
+ + Fixed resource cache flushing
+ + Fixed tld parsing for maven plugin
+ + HttpGenerator can generate requests
+ + Improved *-mbean.properties files and specialized some MBean
+ + JETTY-118 ignore extra content after close.
+ + JETTY-119 cleanedup Security optimizatoin
+ + JETTY-123 handle windows UNC paths
+ + JETTY-126 handle content > Integer.MAX_VALUE
+ + JETTY-129 ServletContextListeners called after servlets are initialized
+ + JETTY-151 Idle timeout only applies to blocking operations
+ + JETTY-154 Cookies are double quotes only
+ + JETTY-171 Fixed filter mapping
+ + JETTY-172 use getName() instead of toString
+ + JETTY-173 restore servletpath after dispatch
+ + Major refactor of SelectChannel EndPoint for client selector
+ + make .tag files work in packed wars
+ + Plugin shutdown context before stopping it.
+ + Refactored session lifecycle and additional tests
+ + release resource lookup in Default servlet
+ + (re)make JAAS classes available to webapp classloader
+ + Reverted UnixCrypt to use coersions (that effected results)
+ + Session IDs can change worker ID
+ + Simplified ResourceCache and Default servlet
+ + SocketConnector closes all connections in doStop
+ + Upgraded session ID generation to use SecureRandom
+ + updated glassfish jasper to tag SJSAS-9_1-B25-EA-08_Nov_2006
+ + Support TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+
+jetty-5.1.14 - 09 August 2007
+ + patched with correct version
+ + JETTY-155 force close with content length.
+ + JETTY-369 failed state in Container
+
+jetty-5.1.13
+ + Sourceforge 1648335: problem setting version for AJP13
+
+jetty-5.1.12 - 22 November 2006
+ + Added support for TLS_DHE_RSA_WITH_AES_256_CBC_SHA
+ + Upgraded session ID generation to use SecureRandom
+ + Quote single quotes in cookies
+ + AJP protected against bad requests from mod_jk
+ + JETTY-154 Cookies ignore single quotes
+
+jetty-4.2.27 - 22 November 2006
+ + Upgraded session ID generation to use SecureRandom
+ + AJP protected against bad requests from mod_jk
+
+jetty-6.1.0pre2 - 20 November 2006
+ + Added extraClassPath to WebAppContext
+ + Fixed resource cache flushing
+ + Clean up jboss module licensing
+
+jetty-6.1.0pre1 - 19 November 2006
+ + Use ContextDeployer as main deployer in jetty.xml
+ + Added extras/jboss
+ + Major refactor of SelectChannel EndPoint for client selector
+ + Fixed NPE in bio.SocketEndPoint.getRemoteAddr()
+ + Reverted UnixCrypt to use coersions (that effected results)
+ + JETTY-151 Idle timeout only applies to blocking operations
+ + alternate optimizations of writer (use -Dbuffer.writers=true)
+ + JETTY-171 Fixed filter mapping
+ + JETTY-172 use getName() instead of toString
+ + JETTY-173 restore servletpath after dispatch
+ + release resource lookup in Default servlet
+ + Simplified ResourceCache and Default servlet
+ + Added override descriptor for deployment of RO webapps
+ + Added hierarchical destroy of mbeans
+
+jetty-6.1.0pre0 - 21 October 2006
+ + add <Property> replacement in jetty xml config files
+ + make .tag files work in packed wars
+ + add hot deployment capability
+ + ensure setContextPath() works when invoked from jetty-web.xml
+ + ensure sessions nulled out on request recycle; ensure session null after
+   invalidate
+ + ensure "" returned for ServletContext.getContextPath() for root context
+ + Fixed tld parsing for maven plugin
+ + Improved *-mbean.properties files and specialized some MBean
+ + Added conversion Object -> ObjectName for the result of method calls made on
+   MBeans
+ + JETTY-129 ServletContextListeners called after servlets are initialized
+ + change examples/test-jndi-webapp so it can be regularly built
+ + added isStopped() in LifeCycle and AbstractLifeCycle
+ + fixed isUserInRole checking for JAASUserRealm
+ + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
+ + add a maven-jetty-jspc-plugin to do jspc precompilation
+ + added examples/test-jaas-webapp
+ + (re)make JAAS classes available to webapp classloader
+ + CGI example in test webapp
+ + Plugin shutdown context before stopping it.
+ + Added concept of bufferred endpoint
+ + Factored ErrorPageErrorHandler out of WebAppContext
+ + Refactored ErrorHandler to avoid statics
+ + Transforming classloader does not transform resources.
+ + SocketConnector closes all connections in doStop
+ + Improved charset handling in URLs
+ + minor optimization of bytes to UTF8 strings
+ + JETTY-112 ContextHandler checks if started
+ + JETTY-113 support optional query char encoding on requests
+ + JETTY-114 removed utf8 characters from code
+ + JETTY-115 Fixed addHeader
+ + added cometd chat demo
+ + JETTY-119 cleanedup Security optimizatoin
+ + Refactored session lifecycle and additional tests
+ + JETTY-121 init not called on externally constructed servlets
+ + JETTY-124 always initialize filter caches
+ + JETTY-126 handle content > Integer.MAX_VALUE
+ + JETTY-123 handle windows UNC paths
+ + JETYY-120 SelectChannelConnector closes all connections on stop
+ + Added ID constructor to AbstractSessionManager.Session
+ + Allow session cookie to be refreshed
+ + Added DataFilter configuration to cometd
+ + Added extras/setuid to support start as root
+ + Apply queryEncoding to getQueryString
+ + JETTY-118 ignore extra content after close.
+ + HttpGenerator can generate requests
+ + Ported HtAccessHandler
+ + Start of a client API
+ + Session IDs can change worker ID
+ + Default soLinger is -1 (disabled)
+ + AJP Connector
+
+jetty-5.1.11 - 08 October 2006
+ + fixed ByteBufferOutputStream capacity calculation
+ + Fixed AJP handling of certificate length (1494939)
+ + Fixed AJP chunk header (1507377)
+ + Fixed order of destruction event calls
+ + Fix to HttpOutputStream from M.Traverso
+ + Default servlet only uses setContentLength on wrapped responses
+
+jetty-4.2.26 - 08 October 2006
+ + Backport of AJP fixes
+
+jetty-6.0.1 - 24 September 2006
+ + fixed isUserInRole checking for JAASUserRealm
+ + fixed ClassCastException in JAASUserRealm.setRoleClassNames(String[])
+ + Improved charset handling in URLs
+ + Factored ErrorPageErrorHandler out of WebAppContext
+ + Refactored ErrorHandler to avoid statics
+ + JETTY-112 ContextHandler checks if started
+ + JETTY-114 removed utf8 characters from code
+ + JETTY-115 Fixed addHeader
+ + JETTY-121 init not called on externally constructed servlets
+ + minor optimization of bytes to UTF8 strings
+ + JETTY-113 support optional query char encoding on requests
+ + JETTY-124 always initialize filter caches
+ + JETYY-120 SelectChannelConnector closes all connections on stop
+
+jetty-6.0.0 - 10 September 2006
+ + SocketConnector closes all connections in doStop
+ + Conveniance builder methods for listeners and filters
+ + Transforming classloader does not transform resources.
+ + Plugin shutdown context before stopping it.
+
+jetty-6.0.0rc4 - 05 September 2006
+ + bind jetty-env.xml entries to java:comp/env
+ + JETTY-107 Poor cast in SessionDump demo.
+ + Set charset on error pages
+
+jetty-6.0.0rc3 - 01 September 2006
+ + pulled 6.0.0 branch
+ + JETTY-103
+ + Move MailSessionReference to org.mortbay.naming.factories
+ + Less verbose handling of BadResources from bad URLs
+ + Avoid double error handling of Bad requests
+ + don't warn for content length on head requests
+ + JETTY-104 (raised glassfish ISSUE-1044) hide JSP forced path attribute
+ + JETTY-68 Complete request after sendRedirect
+ + Transferred the sslengine patch from the patches directory to extras
+
+jetty-6.0.0rc2 - 25 August 2006
+ + use mvn -Dslf4j=false jetty:run to disable use of slf4j logging with
+   jdk1.4/jsp2.0
+ + added org.apache.commons.logging package to system classes that can't be
+   overridden by a webapp classloader
+ + mvn -Djetty.port=x jetty:run uses port number given for the default
+   connector
+ + Fixed NPE when no resource cache
+ + Refactored WebXmlConfiguration to allow custom web.xml resource
+ + Moved more utility packagtes to the util jar
+ + Direct buffer useage is optional
+ + Destroy HttpConnection to improve buffer pooling
+ + Timestamp in StdErrLog
+
+jetty-6.0.0rc1 - 16 August 2006
+ + Support for binding References and Referenceables and javax.mail.Sessions in
+   JNDI
+ + Added TransformingWebAppClassLoader for spring 2.0 byte code modification
+   support
+ + JETTY-90
+ + Fixed FD leak for bad TCP acks. JETTY-63
+ + JETTY-87
+ + Change path mapping so that a path spec of /foo/* does not match /foo.bar :
+   JETTY-88
+ + add <requestLog> config param to jetty plugin
+ + JETTY-85 JETTY-86 (TrustManager and SecureRandom are now configurable;
+   better handling of null/default values)
+ + parse jsp-property-group in web.xml for additional JSP servlet mappings
+ + protected setContentType from being set during include
+ + JETTY-91
+ + added modules/spring with XmlBeanFactory configuration
+ + removed support for lowResources from SelectChannelConnector
+ + added start of cometd implementation (JSON only)
+ + added start of grizzly connector
+ + removed org.mortbay. from context system classes configuration
+ + -DSTOP.PORT must be specified.
+ + moved optional modules to extras
+ + fixed bug that caused Response.setStatus to ignore the provided message
+ + refactored resource cache
+ + Allow direct filling of buffers for uncached static content.
+ + Added simple ResourceHandler and FileServer example
+
+jetty-6.0.0rc0 - 07 July 2006
+ + change prefix from "jetty6" to just "jetty" for plugin: eg is now mvn
+   jetty:run
+ + allow <key> or <name> in <systemProperty> for plugin
+ + simplified jetty.xml with new constructor injections
+ + added setters and getters on SessionManager API for session related config:
+   cookie name, url parameter name, domain, max age and path.
+ + add ability to have a lib/ext dir from which to recursively add all jars and
+   zips to the classpath
+ + patch to allow Jetty to use JSP2.1 from Glassfish instead of Jasper from
+   Tomcat
+ + fixed classesDirectory param for maven plugin to be configurable
+ + ensure explicitly set tmp directory called "work" is not deleted on exit
+ + ensure war is only unpacked if war is newer than "work" directory
+ + change name of generated tmp directory to be
+   "Jetty_"+host+"_"+port+"_"+contextpath+"_"+virtualhost
+ + Cleaned up idle expiry.
+ + Ssl algorithm taken from system property
+ + Added 8 random letters&digits to Jetty-generated tmp work dir name to ensure
+   uniqueness
+ + Simplify runtime resolution of JSP library for plugin
+ + Ensure mvn clean cleans the build
+ + Do not wrap EofException with EofException
+ + reverse order for destroy event listeners
+ + added StatisticsHandler and statistics on Connector.
+ + Simplified Servlet Context API
+ + Added maximum limit to filter chain cache.
+ + refactor HttpChannelEndPoint in preparation for SslEngine
+ + ContextHandlerCollection addContext and setContextClass
+ + Discard excess bytes in header buffer if connection is closing
+ + Updated javax code from
+   http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk/java/javax@417727
+ + Threadpool does not need to be a LifeCycle
+ + support graceful shutdown
+ + Added WebAppContextClassLoader.newInstance to better support exensible
+   loaders.
+ + immutable getParameterMap()
+ + support <load-on-startup> for SingleThreadModel
+ + changed ServletContext.getResourcePaths()  to not return paths containing
+   double slashes
+ + fixed HttpGenerator convertion of non UTF-8: JETTY-82
+ + added html module from jetty 5 - but deprecated until maintainer found
+
+jetty-6.0.0beta17 - 01 June 2006
+ + Added config to disable file memory mapped buffers for windows
+ + Added Request.isHandled()
+ + Refactored Synchronization of SelectChannelConnector
+ + Recovered repository from Codehaus crash
+ + ContextHandler.setConnectors replace setHosts
+ + Connector lowResourceMaxIdleTime  implemented.
+ + Default servlet checks for aliases resources
+ + Added clover reports and enough tests to get >50% coverage
+ + Fixed IE SSL issue.
+ + Implemented runAs on servlets
+ + Flush will flush all bytes rather than just some.
+ + Protected WEB-INF and META-INF
+ + don't reset headers during forward
+ + BoundedThreadPool.doStop waits for threads to complete
+
+jetty-6.0.0beta16 - 12 May 2006
+ + remove a couple of System.err.printlns
+ + replace backwards compativle API in UrlEncoded
+
+jetty-6.0.0beta15 - 11 May 2006
+ + Added Server attribute org.mortbay.jetty.Request.maxFormContentSize
+ + Renamed NotFoundHandler to DefaultHandler
+ + Added automatic scan of all WEB-INF/jetty-*.xml files for plugin
+ + Added <scanTargets> parameter to allow other locations to scan for plugin
+ + Major refactor to simplify Server and handler hierarchy
+ + setSendServerVersion method added to Server to control sending of Server:
+   http header
+ + removed SelectBlockingChannelConnector (unmaintained)
+ + Improved HttpException
+ + Moved more resources to resources
+ + Added ThrottlingFilter and fixed race in Continuations
+ + Added taglib resources to 2.1 jsp api jar
+ + Reset of timer task clears expiry
+ + improved MBeanContainer object removal
+ + ContextHandler.setContextPath can be called after start.
+ + Fixed handling of params after forward
+ + Added --version to start.jar
+ + Added embedded examples
+ + Simplified DefaultServlet static content buffering
+ + readded BoundedThreadPool shrinking (and then fixed resulting deadlock)
+ + improved MBean names
+ + improved support for java5 jconsole
+ + Session scavenger threads from threadpool
+ + Thread names include URI if debug set
+ + don't accept partial authority in request line.
+ + enforce 204 and 304 have no content
+
+jetty-6.0.0beta14 - 09 April 2006
+ + ignore dirs and files that don't exist in plugin scanner
+ + added support for stopping jetty using "java -jar start.jar --stop"
+ + added configurability for webdefault.xml in maven plugin
+ + adding InvokerServlet
+ + added ProxyServlet
+ + stop JDBCUserRealm coercing all credentials to String
+ + Change tmp dir of plugin to work to be in line with jetty convention
+ + Modify plugin to select JSP impl at runtime
+ + Use start.config to select which JSP impl at runtime based on jdk version
+ + Added JSP 2.1 APIs from apache
+ + Added Jasper 2.1 as jesper (jasper without JCL)
+ + Started readding logging to jesper using jdk logging
+ + fixed priority of port from url over host header
+ + implemented request.isUserInRole
+ + securityHandler removed if not used.
+ + moved test webapps to examples directory
+ + improved contentType handling and test harness
+ + fixed forward bug (treated as include)
+ + fixed HttpField iterator
+ + added jetty-util.jar module
+ + added reset to Continuation
+
+jetty-6.0.0beta12 - 16 March 2006
+ + Fixed maven plugin JNDI for redeploys
+ + Fixed tld discovery for plugin (search dependencies)
+ + Fixed JettyPlus for root contexts
+ + Fixed error handling in error page
+ + Added JSP2.0 demos to test webapp
+ + Upgraded jasper to 5.5.15
+ + Added provider support to SslListener
+ + Log ERROR for runtimeExceptions
+
+jetty-6.0.0beta11 - 14 March 2006
+ + added JAAS
+ + added webapp-specific JNDI entries
+ + added missing Configurations for maven plugin
+ + fixed FORM authentication
+ + moved dtd and xsd to standard javax location
+ + added patch to use joda-time
+ + refactored session ID management
+ + refactored configuration files and start()
+ + fixed ; decoding in URIs
+ + Added HttpURI and improved UTF-8 parsing.
+ + refactored writers and improved UTF-8 generation.
+
+jetty-6.0.0beta10 - 25 February 2006
+ + Added support for java:comp/env
+ + Added support for pluggable transaction manager
+ + Forward masks include attributes and vice versa
+ + Fixed default servlet handling of includes
+ + Additional accessors for request logging
+ + added getLocalPort() to connector
+ + Fixed content-type for range requests
+ + Fix for sf1435795 30sec delay from c taylor
+ + Fix for myfaces and include with close
+ + Fix sf1431936 don't chunk the chunk
+ + Fix http://jira.codehaus.org/browse/JETTY-6. hi byte reader
+ + Updates javax to MR2 release
+
+jetty-6.0.0beta9 - 09 February 2006
+ + PathMap for direct context mapping.
+ + Refactored chat demo and upgraded prototype.js
+ + Continuation cleanup
+ + Fixed unraw decoding of query string
+ + Fixed dispatch of wrapped requests.
+ + Fixed double flush of short content.
+ + Added request log.
+ + Added CGI servlet.
+ + Force a tempdir to be set.
+ + Force jasper scratch dir.
+ + fixed setLocale bug sf1426940
+ + Added TLD tag listener handling.
+
+jetty-6.0.0beta8 - 24 January 2006
+ + fixed dispatch of new session problem. sf:1407090
+ + reinstated rfc2616 test harness
+ + Handle pipeline requests without hangs
+ + Removed queue from thread pool.
+ + improved caching of content types
+ + fixed bug in overloaded write method on HttpConnection (reported against
+   Tapestry4.0)
+ + hid org.apache.commons.logging and org.slf4j packages from webapp
+ + maven-jetty6-plugin stopped transitive inclusion of log4j and
+   commons-logging from commons-el for jasper
+ + patch to remove spurious ; in HttpFields
+ + improve buffer return mechanism.
+ + conveniance addHandler removeHandler methods
+ + maven-jetty6-plugin: ensure compile is done before invoking jetty
+ + maven-jetty6-plugin: support all types of artifact dependencies
+
+jetty-6.0.0Beta7
+ + Fixed infinite loop with chunk handling
+ + Faster header name lookup
+ + removed singleton Container
+ + reduced info verbosity
+ + null dispatch attributes not in names
+ + maven-jetty6-plugin added tmpDirectory property
+ + maven-jetty6-plugin stopped throwing an error if there is no target/classes
+   directory
+
+jetty-6.0.0Beta6
+ + Fixed issue with blocking reads
+ + Fixed issue with unknown headers
+ + optimizations
+
+jetty-6.0.0Beta5
+ + Moved to SVN
+ + Fixed writer char[] creations
+ + Added management module for mbeans
+
+jetty-6.0.0Beta4
+ + System property support in plugin
+ + CVE-2006-2758 Fixed JSP visibility security issue.
+ + Improved jetty-web.xml access to org.mortbay classes.
+ + Jasper 5.5.12
+
+jetty-6.0.0Beta3
+ + Fixed error in block read
+ + Named dispatch.
+ + Fixed classloader issue with server classes
+
+jetty-6.0.0Beta2
+ + merged util jar back into jetty jar
+ + Simpler continuation API
+ + loosely coupled with JSP servlet
+ + loosely coupled with SLF4J
+ + Improved reuse of HttpField values and cookies.
+ + Improved buffer return
+
+jetty-6.0.0Beta1
+ + Servlet 2.5 API
+ + SSL connector
+ + maven2 plugin
+ + shutdown hook
+ + refactored start/stop
+ + Implemented all listeners
+ + Error pages
+ + Virtual hosts
+ + Multiple select sets
+
+jetty-6.0.0Beta0
+ + Maven 2 build
+ + Dispatcher parameters
+ + UTF-8 encoding for URLs
+ + Fixed blocking read
+
+jetty-6.0.0APLPA3
+ + Added demo for Continuations
+ + Jasper and associated libraries.
+
+jetty-6.0.0ALPHA2
+ + Continuations - way cool way to suspend a request and retry later.
+ + Dispatchers
+ + Security
+
+jetty-6.0.0ALPHA1
+ + Filters
+ + web.xml handling
+
+jetty-6.0.0ALPHA0
+ + Totally rearchitected and rebuilt, so 10 years of cruft could be removed!
+ + Improved "dependancy injection" and "inversion of control" design of
+   components
+ + Improved "interceptor" design of handlers
+ + Smart split buffer design allows large buffers to only be allocated to
+   active connections. The resulting memory savings allow very large buffers to
+   be used, which increases the chance of efficient asynchronous flushing and
+   of avoiding chunking.
+ + Optional use of NIO Buffering so that efficient direct buffers and memory
+   mapped files can be used.
+ + Optional use of NIO non-blocking scheduling so that threads are not
+   allocated per connection.
+ + Optional use of NIO gather writes, so that for example a HTTP header and a
+   memory mapped
+ + file may be sent as sent is a single operation.
+ + Missing Security
+ + Missing Request Dispatchers
+ + Missing web.xml based configuration
+ + Missing war support
+
+jetty-5.1.11RC0 - 05 April 2006
+ + stop JDBCUserRealm forcing all credentials to be String
+ + force close with shutdownOutput for win32
+ + NPE protection if desirable client certificates
+ + Added provider support to SslListener
+ + logging improvements for servlet and runtime exceptions
+ + Fixed AJP handling of ;jsessionid.
+ + improved contentType param handling
+
+jetty-5.1.10 - 05 January 2006
+ + Fixed path aliasing with // on windows.
+ + Fix for AJP13 with multiple headers
+ + Fix for AJP13 with encoded path
+ + Remove null dispatch attributes from getAttributeNames
+ + Put POST content default back to iso_8859_1. GET is UTF-8 still
+
+jetty-4.2.25 - 04 January 2006
+ + Fixed aliasing of // for win32
+
+jetty-5.1.9 - 07 December 2005
+ + Fixed wantClientAuth(false) overriding netClientAuth(true)
+
+jetty-6.0.0betaX
+ + See http://jetty.mortbay.org/jetty6 for 6.0 releases
+
+jetty-5.1.8 - 07 December 2005
+ + Fixed space in URL issued created in 5.1.6
+
+jetty-5.1.7 - 07 December 2005
+
+jetty-5.1.7rc0 - 06 December 2005
+ + improved server stats
+ + char encoding for MultiPartRequest
+ + fixed merging of POST params in dispatch query string.
+ + protect from NPE in dispatcher getValues
+ + Updated to 2.6.2 xerces
+ + JSP file servlet mappings copy JspServlet init params.
+ + Prefix servlet context logs with org.mortbay.jetty.context
+ + better support for URI character encodings
+ + use commons logging jar instead of api jar.
+
+jetty-5.1.6 - 18 November 2005
+ + CVE-2006-2758 Fixed JSP visibility security issue.
+ + Improved jetty-web.xml access to org.mortbay classes.
+
+jetty-5.1.5 - 10 November 2005
+ + Improved shutdown hook
+ + Improved URL Decoding
+ + Improved mapping of JSP files.
+
+jetty-5.1.5rc2 - 07 October 2005
+ + Reverted dispatcher params to RI rather than spec behaviour.
+ + ProxyHandler can handle chained proxies
+ + unsynchronized ContextLoader
+ + ReFixed merge of Dispatcher params
+ + public ServerMBean constructor
+ + UTF-8 encoding for URLs
+ + Response.setLocale will set locale even if getWriter called.
+
+jetty-5.1.5rc1 - 23 August 2005
+ + upgraded to commons logging 1.0.4
+ + Release commons logging factories when stopping context.
+ + Fixed illegal state with chunks and 100 continue - Tony Seebregts
+ + Fixed PKCS12Import input string method
+ + Fixed merge of Dispatcher parameters
+ + Encoded full path in ResourceHandler directory listing
+ + handle extra params after charset in header
+ + Fixed 100-continues with chunking and early commit
+
+jetty-5.1.5rc0 - 16 August 2005
+ + Fixed component remove memory leak for stop/start cycles
+ + Facade over commons LogFactory so that discovery may be avoided.
+ + Applied ciphersuite patch from tonyj
+ + Authenticators use servlet sendError
+ + CGI sets SCRIPT_FILENAME
+ + HttpTunnel timeout
+ + NPE protection for double stop in ThreadedServer
+ + Expect continues only sent if input is read.
+
+jetty-5.1.4 - 05 June 2005
+ + Fixed FTP close issue.
+ + setup MX4J with JDK1.5 in start.config
+ + set classloader during webapp doStop
+ + NPE protection in ThreadedServer
+ + ModelMBean handles null signatures
+ + Change JAAS impl to be more flexible on finding roles
+
+jetty-5.1.4rc0 - 19 April 2005
+ + ServletHttpContext correctly calls super.doStop.
+ + HttpServer delegates component handling to Container.
+ + Allow ServletHandler in normal HttpContext again.
+ + Stop start.jar putting current directory on classpath.
+ + More protection from null classloaders.
+ + Turn off web.xml validation for JBoss.
+
+jetty-5.1.3 - 07 April 2005
+ + Some minor code janitorial services
+
+jetty-4.2.24 - 07 April 2005
+
+jetty-5.1.3rc4 - 31 March 2005
+ + Moved servlet request wrapping to enterContextScope for geronimo security
+ + refixed / mapping for filters
+ + Allow XmlConfiguration to start with no object.
+ + updated to mx4j 3.0.1
+ + rework InitialContextFactory to use static 'default' namespace
+ + make java:comp/env immutable for webapps as per J2EE spec
+
+jetty-5.1.3rc3 - 20 March 2005
+ + removed accidental enablement of DEBUG for JettyPlus jndi in
+   log4j.properties
+ + fixed "No getter or setter found" mbean errors
+
+jetty-5.1.3rc2 - 16 March 2005
+ + Updated JSR154Filter for ERROR dispatch
+ + Fixed context to _context refactory error
+
+jetty-5.1.3rc1 - 13 March 2005
+ + Fixed typo in context-param handling.
+ + update to demo site look and feel.
+ + Fixed principal naming in FormAuthenticator
+ + JettyPlus updated to JOTM 2.0.5, XAPool 1.4.2
+
+jetty-4.2.24rc1
+ + Fixed principal naming in FormAuthenticator
+
+jetty-5.1.3rc0 - 08 March 2005
+ + Flush filter chain caches on servlet/filter change
+ + Fixed rollover filename format bug
+ + Fixed JSR154 error dispatch with explicit pass of type.
+ + Allow system and server classes to be configured for context loader.
+ + IOException if EOF read during chunk.
+ + Fixed HTAccess crypt salt handling.
+ + Added simple xpath support to XmlParser
+ + Added TagLibConfiguration to search for listeners in TLDs.
+ + Added SslListener for 1.4 JSSE API.
+ + Fixed moderate load preventing ThreadPool shrinking.
+ + Added logCookie and logLatency support to NCSARequestLog
+ + Added new JAAS callback to allow extra login form fields in authentication
+
+jetty-4.2.24rc0 - 08 March 2005
+ + Back ported Jetty 5 ThreadedServer and ThreadPool
+ + Added logCookie and logLatency support to NCSARequestLog
+
+jetty-5.1.2 - 18 January 2005
+ + Added id and ref support to XmlConfiguration
+ + Cleaned up AbstractSessionManager synchronization.
+ + Fixed potential concurrent login problem with JAAS
+ + Apply patch #1103953
+
+jetty-4.2.23 - 16 January 2005
+ + Cleaned up AbstractSessionManager synchronization.
+ + Fixed potential concurrent login problem with JAAS
+
+jetty-5.1.2pre0 - 22 December 2004
+ + Fixed case of Cookie parameters
+ + Support Secure and HttpOnly in session cookies
+ + Modified useRequestedID handling to only use IDs from other contexts
+ + Added global invalidation to AbstractSessionManager
+ + UnavailableException handling from handle
+ + Fixed suffix filters
+
+jetty-4.2.23RC0 - 17 December 2004
+ + LineInput handles readers with small internal buffer
+ + Added LogStream to capture stderr and stdout to logging
+ + Support Secure and HttpOnly in session cookies
+ + Build unsealed jars
+
+jetty-5.1.1 - 01 December 2004
+
+jetty-5.1.1RC1
+ + Some minor findbugs code cleanups
+ + Made more WebApplicationHandle configuration methods public.
+ + Fixed ordering of filters with multiple interleaved mappings.
+ + Allow double // within URIs
+ + Applied patch for MD5 hashed credentials for MD5
+
+jetty-5.1.1RC0 - 17 November 2004
+ + fix for adding recognized EventListeners
+ + fix commons logging imports to IbmJsseListener
+ + added new contributed shell start/stop script
+ + excluded ErrorPageHandler from standard build in extra/jdk1.2 build
+
+jetty-5.1.0 - 14 November 2004
+
+jetty-5.1.RC1 - 24 October 2004
+ + Allow JSSE listener to be just confidential or just integral.
+ + Fixed NPE for null contenttype
+ + improved clean targets
+ + when committed setHeader is a noop rather than IllegalStateException
+ + Partially flush writers on every write so content length can be detected.
+ + Build unsealed jars
+ + default / mapping does not apply to Filters
+ + many minor cleanups suggested from figbug utility
+ + Allow multiple accepting threads
+
+jetty-5.1.RC0 - 11 October 2004
+ + Fixed many minor issues from J2EE 1.4 TCK testing See sf.net bugs 1031520 -
+   1032205
+ + Refactored, simplified and optimized HttpOutputStream
+ + LineInput handles readers with small internal buffer
+ + Added LogStream to capture stderr and stdout to logging
+ + Added filter chain cache
+ + Added JSR77 servlet statistic support
+ + Refactored webapp context configurations
+ + Added LifeCycle events and generic container.
+ + Upgraded to ant-1.6 for jasper
+ + Fixed HTAccessHandler
+ + JBoss 4.0.0 support
+
+jetty-5.0.0 - 10 September 2004
+
+jetty-5.0.RC4 - 05 September 2004
+ + Fixed configuration of URL alias checking
+ + JettyJBoss: Use realm-name from web.xml if present, otherwise use
+   security-domain from jboss-web.xml
+
+jetty-5.0.RC3 - 28 August 2004
+ + DIGEST auth handles qop, stale and maxNonceAge.
+ + Less verbose warning for non validating xml parser.
+ + fixed jaas logout for jetty-jboss
+ + fixed deployment of ejb-link elements in web.xml with jboss
+ + Update to jasper 5.0.27
+ + Added parameters for acceptQueueSize and lowResources level.
+ + Changed default URI encoding to UTF-8
+ + Fixes to work with java 1.5
+ + JettyPlus upgrade to XAPool 1.3.3. and HSQLDB 1.7.2
+ + JettyPlus addition of pluggable DataSources
+ + Always say close for HTTP/1.0 non keep alive.
+
+jetty-4.2.22
+ + fixed jaas logout for jetty-jboss integration
+ + fixed deployment of ejb-link elements in web.xml for jboss
+ + Added parameters for acceptQueueSize and lowResources level.
+
+jetty-5.0.RC2 - 02 July 2004
+ + Fixed DIGEST challenge delimiters
+ + HTAccess calls UnixCrypt correctly
+ + integrated jetty-jboss with jboss-3.2.4
+ + Error dispatchers are always GET requests.
+ + OPTIONS works for all URLs on default servlet
+ + add JMX support for JettyPlus
+ + add listing of java:comp/env for webapp with JMX
+ + make choice of override of JNDI ENC entries: config.xml or web.xml
+ + Default servlet may use only pathInfo for resource
+ + Fixed session leak in j2ee
+ + Fixed no-role security constraint combination.
+ + Fix to use runas roles during servlet init and destroy
+ + Fixed JAAS logout
+ + HttpContext sendError for authentication errors
+
+jetty-4.2.21 - 02 July 2004
+ + integrated jetty-jboss with jboss-3.2.4
+ + add JMX support for JettyPlus
+ + add listing of java:comp/env for webapp with JMX
+ + make choice of override of JNDI ENC entries: config.xml or web.xml
+ + Fixed JAAS logout
+
+jetty-5.0.RC1 - 24 May 2004
+ + Changed to apache 2.0 license
+ + added extra/etc/start-plus.config to set up main.class for jettyplus
+ + maxFormContentLength may be unlimited with <0 value
+ + Fixed HTTP tunnel timeout setting.
+ + Improved handling of exception from servlet init.
+ + FORM auth redirects to context on a re-auth
+ + Handle multiple virutal hosts from JBoss 3.2.4RC2
+
+jetty-4.2.20 - 22 May 2004
+ + maxFormContentLength may be unlimited with <0 value
+ + Fixed HTTP tunnel timeout setting.
+ + Improved handling of exception from servlet init.
+ + FORM auth redirects to context on a re-auth
+
+jetty-5.0.0RC0 - 07 April 2004
+ + Updated JettyPlus to JOTM 1.4.3 (carol-1.5.2, xapool-1.3.1)
+ + ServletContext attributes wrap HttpContext attributes.
+ + Factored out XML based config from WebApplicationContext
+ + Improved RequestLog performance
+ + Fixed j2se 1.3 problem with HttpFields
+ + Default servlet respectes servlet path
+ + Fixed setCharacterEncoding for parameters.
+ + Fixed DOS problem
+ + Worked around bad jboss URL handler in XMLParser
+ + Forced close of connections over stop/start
+ + ProxiedFor field support added to NCSARequestLog
+ + Fixed Default servlet for non empty servlet paths
+ + Updated mx4j to V2
+ + Updated jasper to 5.0.19
+ + Changed dist naming convention to lowercase
+
+jetty-4.2.20RC0 - 07 April 2004
+ + Worked around bad jboss URL handler in XMLParser
+ + Forced close of connections over stop/start
+ + HttpFields protected headers
+ + ProxiedFor field support added to NCSARequestLog
+ + Fixed Default servlet for non empty servlet paths
+ + Changed dist naming convention to lowercase
+
+jetty-4.2.19 - 19 March 2004
+ + Fixed DOS attack problem
+
+jetty-5.0.beta2 - 12 February 2004
+ + Added skeleton JMX MBean for jetty plus
+ + Fixed HEAD with empty chunk bug.
+ + Fixed jetty.home/work handling
+ + Fixed setDate thread safety
+ + Fixed SessionManager init
+ + Improved low thread handling
+ + FileResource better handles non sun JVM
+ + Monitor closes socket before exit
+ + Updated to Japser 5.0.16
+ + RequestDispatcher uses request encoding for query params
+ + Fixed busy loop in threadpool run
+ + Reorganized ServletHolder init
+ + Added log4j context repository to jettyplus
+ + NPE guard for no-listener junit deployment
+ + Added experimental NIO listeners again.
+ + fixed filter dispatch configuration.
+ + fixed lazy authentication with FORMs
+
+jetty-4.2.18 - 01 March 2004
+ + Added log4j context repository to jettyplus
+ + NPE guard for no-listener junit deployment
+ + Improved log performance
+ + Fixed j2se 1.3 problem with HttpFields
+ + Suppress some more IOExceptions
+ + Default servlet respectes servlet path
+
+jetty-4.2.17 - 01 February 2004
+ + Fixed busy loop in threadpool run
+ + Reorganized ServletHolder init
+
+jetty-4.2.16 - 30 January 2004
+ + Fixed setDate multi-cpu race
+ + Improved low thread handling
+ + FileResource better handles non sun JVM
+ + Fixed HttpTunnel for JDK 1.2
+ + Monitor closes socket before exit
+ + RequestDispatcher uses request encoding for query params
+ + Update jasper to 4.1.29
+
+jetty-5.0.beta1 - 24 December 2003
+ + SecurityConstraints not reset by stop() on custom context
+ + Fixed UnixCrypt handling in HTAccessHandler
+ + Added patch for JBoss realm single sign on
+ + Reorganized FAQ
+ + Env variables for CGI
+ + Removed support for old JBoss clustering
+
+jetty-4.2.15 - 24 December 2003
+ + SecurityConstraints not reset by stop() on custom context
+ + Fixed UnixCrypt handling in HTAccessHandler
+ + Added patch for JBoss realm single sign on
+ + Environment variables for CGI
+ + Removed support for old JBoss clustering
+
+jetty-5.0.beta0 - 22 November 2003
+ + Removed support for HTTP trailers
+ + PathMap uses own Map.Entry impl for IBM JVMs
+ + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
+ + Protect ThreadPool.run() from interrupted exceptions
+ + Added org.mortbay.http.ErrorHandler for error pages.
+ + Fixed init race in HttpFields cache
+ + Allow per listener handlers
+ + Added MsieSslHandler to handle browsers that don't grok persistent SSL (msie
+   5)
+ + Respect content length when decoding form content.
+ + JBoss integration uses writer rather than stream for XML config handling
+ + Expire pages that contain set-cookie as per RFC2109 recommendation
+ + Updated jasper to 5.0.14beta
+ + Removed the CMR/CMP distributed session implementation
+
+jetty-4.2.15rc0 - 22 November 2003
+ + PathMap uses own Map.Entry impl for IBM JVMs
+ + Race in HttpFields cache
+ + Use ${jetty.home}/work or WEB-INF/work for temp directories if present
+ + Protect ThreadPool.run() from interrupted exceptions
+ + Added org.mortbay.http.ErrorHandler for error pages.
+ + JsseListener checks UserAgent for browsers that can't grok persistent SSL
+   (msie5)
+ + Removed the CMR/CMP distributed session implementation
+
+jetty-4.2.14 - 04 November 2003
+ + respect content length when decoding form content.
+ + JBoss integration uses writer rather than stream for XML config handling
+ + Fixed NPE in SSO
+ + Expire pages that contain set-cookie as per RFC2109 recommendation
+
+jetty-5.0.alpha3 - 19 October 2003
+ + Reworked Dispatcher to better support cross context sessions.
+ + Use File.toURI().toURL() when jdk 1.2 alternative is available.
+ + Priority added to ThreadPool
+ + replaced win32 service with http://wrapper.tanukisoftware.org
+ + FileClassPath derived from walk of classloader hierarchy.
+ + Implemented security constraint combinations
+ + Set TransactionManager on JettyPlus datasources and pools
+ + Fixed null pointer if no sevices configured for JettyPlus
+ + Updated jasper and examples to 5.0.12
+ + Lazy authentication if no auth constraint.
+ + Restore servlet handler after dispatch
+ + Allow customization of HttpConnections
+ + Failed requests excluded from duration stats
+
+jetty-4.2.14RC1 - 19 October 2003
+ + Reworked Dispatcher to better support cross context sessions.
+ + Added UserRealm.logout and arrange for form auth
+ + Allow customization of HttpConnections
+ + Failed requests excluded from
+
+jetty-4.2.14RC0 - 07 October 2003
+ + Correctly setup context classloader in cross context dispatch.
+ + Put a semi busy loop into proxy tunnels for IE problems
+ + Fixed handling of error pages for IO and Servlet exceptions
+ + updated extra/j2ee to jboss 3.2.1+
+ + Use File.toURI().toURL() when jdk 1.2 alternative is available.
+ + cookie timestamps are in GMT
+ + Priority on ThreadedServer
+ + replaced win32 service with http://wrapper.tanukisoftware.org
+ + Build fileclasspath from a walk of the classloaders
+ + Set TransactionManager on JettyPlus datasources and pools
+ + Fixed null pointer if no sevices configured for JettyPlus
+ + Fixed comments with embedded double dashes on jettyplus.xml file
+
+jetty-5.0.alpha2 - 19 September 2003
+ + Use commons logging.
+ + Use log4j if extra is present.
+ + Improved JMX start.
+ + Update jakarta examples
+ + Correctly setup context classloader in cross context dispatch.
+ + Turn off validation without non-xerces errors
+ + minor doco updates.
+ + moved mailing lists to sourceforge.
+ + Put a semi busy loop into proxy tunnels for IE problems
+ + MultipartRequest supports multi value headers.
+ + XML entity resolution uses URLs not Resources
+ + Implemented ServletRequestListeners as optional filter.
+ + Moved error page mechanism to be webapp only.
+ + Fixed error page handling of IO and Servlet exceptions.
+
+jetty-5.0.alpha1 - 12 August 2003
+ + Switched to mx4j
+ + Improve combinations of Security Constraints
+ + Implemented locale encoding mapping.
+ + Synced with 4.2.12
+ + Updated to Jasper 5.0.7
+ + Server javadoc from war
+
+jetty-5.0.alpha0 - 16 July 2003
+ + Compiled against 2.4 servlet spec.
+ + Implemented remote/local addr/port methods
+ + Updated authentication so that a normal Principal is used.
+ + updated to jasper 5.0.3
+ + Implemented setCharaterEncoding
+ + Implemented filter-mapping <dispatcher> element
+ + Implemented Dispatcher forward attributes.
+
+jetty-4.2.12 - 12 August 2003
+ + Restore max inactive interval for session manager
+ + Removed protection of org.mortbay.http attributes
+ + Fixed parameter ordering for a forward request.
+ + Fixed up HTAccessHandler
+ + Improved error messages from ProxyHandler
+ + Added missing S to some OPTIONS strings
+ + Added open method to threaded server.
+ + FORMAuthenticator does 403 with empty error page.
+ + Fixed MIME types for chemicals
+ + Padding for IE in RootNotFoundHandler
+
+jetty-4.2.11 - 12 July 2003
+ + Fixed race in servlet initialization code.
+ + Cookie params all in lower case.
+ + Simplified AJP13 connection handling.
+ + Prevent AJP13 from reordering query.
+ + Support separate Monitor class for start
+ + Branched for Jetty 5 development.
+
+jetty-4.2.10 - 07 July 2003
+ + Updates to JettyPlus documentation
+ + Updates to Jetty tutorial for start.jar, jmx etc
+
+jetty-4.2.10pre2 - 04 July 2003
+ + Improvement to JettyPlus config of datasources and connection pools
+ + Addition of mail service for JettyPlus
+ + Move to Service-based architecture for JettyPlus features
+ + Re-implementation of JNDI
+ + Many improvements in JettyPlus java:comp handling
+ + Allow multiple security-role-ref elements per servlet.
+ + Handle Proxy-Connection better
+ + Cleaned up alias handling.
+ + Confidential redirection includes query
+ + handle multiple security role references
+ + Fixed cookie handling for old cookies and safari
+ + Restricted ports in ProxyHandler.
+ + URI always encodes %
+ + Session statistics
+ + XmlConfiguration can get/set fields.
+
+jetty-4.2.10pre1 - 02 June 2003
+ + Fixed JSP code visibility problem introduced in Jetty-4.2.10pre0
+ + Added stop.jar
+ + Added SSO implementation for FORM authentication.
+ + WebApplicationContext does not reassign defaults descriptor value.
+ + Fixed AJP13 protocol so that request/response header enums are correct.
+ + Fixed form auth success redirect after retry, introduced in 4.2.9rc1
+ + Trace support is now optional (in AbstractHttpHandler).
+ + Deprecated forced chunking.
+ + Form authentication remembers URL over 403
+ + ProxyHandler has improved test for request content
+ + Removed support of org.mortbay.http.User role.
+ + Fixed problem with shared session for inter context dispatching.
+
+jetty-4.2.10pre0 - 05 May 2003
+ + Moved Log4JLogSink into JettyPlus
+ + Added ability to override jetty startup class by using -Djetty.server on
+   runline
+ + Incorporate JettyPlus jotm etc into build.
+ + Massive reorg of the CVS tree.
+ + Incorporate jetty extra and plus into build
+ + Integrate with JAAS
+ + Apply the append flag of RolloverFileOutputStream constructor.
+ + RolloverFileOutputStream manages Rollover thread.
+ + New look and feel for www site.
+ + Fixed table refs in JDBCUserRealm.
+ + Allow params in form auth URLs
+ + Updated to jasper jars from tomcat 4.1.24
+ + Allow query params in error page URL.
+ + ProxyHandler checks black and white lists for Connect.
+ + Merge multivalued parameters in dispatcher.
+ + Fixed CRLF bug in MultiPartRequest
+ + Warn if max form content size is reached.
+ + getAuthType returns CLIENT_CERT instead of CLIENT-CERT.
+ + getAuthType maps the HttpServletRequest final strings.
+ + FORM Authentication is serializable for session distribution.
+
+jetty-4.2.9 - 19 March 2003
+ + Conditional headers check after /dir to /dir/ redirection.
+
+jetty-4.2.9rc2 - 16 March 2003
+ + Fixed build.xml for source release
+ + Made rfc2068 PUT/POST Continues support optional.
+ + Defaults descriptor has context classloader set.
+ + Allow dispatch to j_security_check
+ + Added X-Forwarded-For header in ProxyHandler
+ + Updated included jmx jars
+
+jetty-4.2.9rc1 - 06 March 2003
+ + Work around URLClassloader not handling leading /
+ + Dump servlet can load resources for testing now.
+ + Added trust manager support to SunJsseListener.
+ + Added support for client certs to AJP13.
+ + Cleaned up includes
+ + Removed checking for single valued headers.
+ + Optional 2.4 behaviour for sessionDestroyed notification.
+ + Stop proxy url from doing user interaction.
+ + Turn request log buffering off by default.
+ + Reduced default context cache sizes (Total 1MB file 100KB).
+ + ProxyHandler has black and white host list.
+ + Added requestlog to HttpContext.
+ + Allow delegated creation of WebApplication derivations.
+ + Check Data contraints before Auth constraints
+
+jetty-4.2.8_01 - 18 February 2003
+ + Patched first release of 4.2.8 with correct version number
+ + Fixed CGI servlet to handle multiple headers.
+ + Added a SetResponseHeadersHandler, can set P3P headers etc.
+ + ProxyHandler can handle multiple cookies.
+ + Fixed AdminServlet to handle changed getServletPath better.
+ + Default servlet can have own resourceBase.
+ + Rolled back SocketChannelListener to 4.2.5 version
+ + Added option to resolve remote hostnames.  Defaults to off.
+ + Added MBeans for Servlets and Filters
+ + Moved ProxyHandler to the src1.4 tree
+
+jetty-4.2.7 - 04 February 2003
+ + Upgraded to JSSE 1.0.3_01 to fix security problem.
+ + Fixed proxy tunnel for non persistent connections.
+ + Relative sendRedirect handles trailing / correctly.
+ + Changed PathMap to conform to / getServletPath handling.
+
+jetty-4.2.6 - 24 January 2003
+ + Improved synchronization on AbstractSessionManager.
+ + Allow AJP13 buffers to be resized.
+ + Fixed LineInput problem with expanded buffers.
+ + ClientCertAuthentication updates request.
+ + Fixed rel sendRedirects for root context.
+ + Added HttpContext.setHosts to restrict context by real interface.
+ + Added MBeans for session managers
+ + Improved SocketChannelListener contributed.
+ + Added version to HttpServerMBean.
+
+jetty-4.2.5 - 14 January 2003
+ + Fixed pathParam bug for ;jsessionid
+ + Don't process conditional headers and ranges for includes
+ + Added Log4jSink in the contrib directory.
+ + Fixed requestedSessionId null bug.
+
+jetty-4.2.4 - 04 January 2003
+ + Fixed stop/start handling of servlet context
+ + Reuse empty LogSink slots.
+ + HTAccessHandler checks realm as well as htpassword.
+ + Clear context listeners after stop.
+ + Clear context attributes after stop.
+ + Use requestedSessionId as default session ID.
+ + Added MBeans for handlers
+ + Upgraded jasper to 4.1.18
+
+jetty-4.2.4rc0 - 12 December 2002
+ + Simplified ThreadedServer
+ + Use ThreadLocals for ByteArrayPool to avoid synchronization.
+ + Use Version to reset HttpFields
+ + Cheap clear for HttpFields
+ + Fixed setBufferSize NPE.
+ + Cleaned up some unused listener throws.
+ + Handle chunked form data.
+ + Allow empty host header.
+ + Avoid optional 100 continues.
+ + Limit form content size.
+ + Handle = in param values.
+ + Added HttpContext.flushCache
+ + Configurable root context.
+ + RootNotFoundHandler to help when no context found.
+ + Update jasper to 4.1.16beta
+ + Fixed dir listing from jars.
+ + Dir listings in UTF8
+ + Character encoding handling for GET requests.
+ + Removed container transfer encoding handling.
+ + Improved setBufferSize handling
+ + Code logs objects rather than strings.
+ + Better access to session manager.
+ + Fixed isSecure and getScheme for SSL over AJP13
+ + Improved ProxyHandler to the point is works well for non SSL.
+ + Implemented RFC2817 CONNECT in ProxyHandler
+ + Added gzip content encoding support to Default and ResourceHandler
+
+jetty-4.2.3 - 02 December 2002
+ + Removed aggressive threadpool shrinkage to avoid deadlock on SMP machines.
+ + Fixed some typos
+ + Added links to Jetty Powered page
+ + Clean up of ThreadedServer.stop()
+ + Updated bat scripts
+ + Added PKCS12Import class to import PKCS12 key directly
+ + removed old HttpContext.setDirAllowed()
+ + added main() to org.mortbay.http.Version
+ + Check form authentication config for leading /
+ + Cleaner servlet stop to avoid extra synchronization on handle
+ + org.mortbay.http.HttpContext.FileClassPathAttribute
+
+jetty-4.2.2 - 20 November 2002
+ + Fixed sendRedirect for non http URLS
+ + Fixed URI query recycling for persistent connections
+ + Fixed handling of empty headers
+ + Added EOFException to reduce log verbosity on closed connections.
+ + Avoided bad buffer status after closed connection.
+
+jetty-4.2.1 - 18 November 2002
+ + Fixed bad optimization in UrlEncoding
+ + Re-enabled UrlEncoding test harnesses
+
+jetty-4.2.0 - 16 November 2002
+ + Fixed AJP13 buffer size.
+ + Fixed remove listener bug.
+ + Fixed include of Invoker servlet.
+ + Restrict 304 responses to seconds time resolution.
+ + Use IE date formatting for speed.
+ + Removed jasper source and just include jars from 4.1.12
+ + Worked around JVM1.3 bug for JSPs
+ + Lowercase jsessionid for URLs only.
+ + Made NCSARequestLog easier to extend.
+ + Added definitions for RFC2518 WebDav response codes.
+ + Removed remaining non portable getBytes() calls
+ + Added upload demo to dump servlet.
+ + Many more optimizations.
+
+jetty-4.1.4 - 16 November 2002
+ + Fixed ContextLoader parent delegation bug
+ + Fixed remove SocketListener bug.
+ + Fixed Invoker servlet for RD.include
+ + Use IE date formatting for last-modified efficiency
+ + Last modified handling uses second resolution.
+ + Made NCSARequestLog simpler to extend.
+
+jetty-4.2.0rc1 - 02 November 2002
+ + Support default mime mapping defined by *
+ + Recycling of HttpFields class.
+ + Renamed Filter application methods.
+ + Fixed firstWrite after commit.
+ + Fixed ContextLoader parent delegation bug.
+ + Fixed problem setting the size of chunked buffers.
+ + Removed unused Servlet and Servlet-Engine headers.
+ + Fixed servletpath on invoker for named servlets.
+ + Fixed directory resource bug in JarFileResource.
+ + Improved handling of 2 byte encoded characters within forms.
+
+jetty-4.2.0rc0 - 24 October 2002
+ + Greg's birthday release!
+ + Added embedded iso8859 writer to HttpOutputStream.
+ + Removed duplicate classes from jar
+ + Fixed RolloverFileOutputStream without date.
+ + Fixed SessionManager initialization
+ + Added authenticator to admin.xml
+ + Fixed Session timeout NPE.
+
+jetty-4.1.3 - 24 October 2002
+ + Fixed RolloverFileOutputStream without date.
+ + Fixed SessionManager initialization
+ + Added authenticator to admin.xml
+ + Fixed Session timeout NPE.
+
+jetty-4.0.6 - 24 October 2002
+ + Clear interrupted status in ThreadPool
+ + Fixed forward query string handling
+ + fixed forward attribute handling for jsp-file servlets
+ + Fixed setCharacterEncoding to work with getReader
+ + Fixed handling of relative sendRedirect after forward.
+ + Fixed virtual hosts temp directories.
+
+jetty-4.2.0beta0 - 13 October 2002
+ + New ThreadPool implementation.
+ + New Buffering implementation.
+ + New AJP13 implementation.
+ + Removed Dispatcher dependancy on ServletHttpContext
+ + getNamedDispatcher(null) returns containers default servlet.
+ + unquote charset in content type
+ + Stop/Start filters in declaration order.
+ + Use "standard" names for default,jsp & invoker servlets.
+ + Fixed caching of directories to avoid shared buffers.
+ + Fixed bad log dir detection
+ + Fix Session invalidation bug
+ + Build without jmx
+ + 404 instead of 403 for WEB-INF requests
+ + FORM authentication sets 403 error page
+ + Allow %3B encoded ; in URLs
+ + Allow anonymous realm
+ + Update jasper to 4.1.12 tag
+
+jetty-4.1.2 - 13 October 2002
+ + Some AJP13 optimizations.
+ + getNamedDispatcher(null) returns containers default servlet.
+ + unquote charset in content type
+ + Stop/Start filters in declaration order.
+ + Use "standard" names for default,jsp & invoker servlets.
+ + Fixed caching of directories to avoid shared buffers.
+ + Fixed bad log dir detection
+ + Fix Session invalidation bug
+ + Build without jmx
+ + 404 instead of 403 for WEB-INF requests
+ + FORM authentication sets 403 error page
+ + Allow %3B encoded ; in URLs
+ + Allow anonymous realm
+ + Update jasper to 4.1.12 tag
+
+jetty-4.1.1 - 30 September 2002
+ + Fixed client scripting vulnerability with jasper2.
+ + Merged LimitedNCSARequestLog into NCSARequestLog
+ + Fixed space in resource name handling for jdk1.4
+ + Moved launcher/src to src/org/mortbay/start
+ + Fixed infinite recursion in JDBCUserRealm
+ + Avoid setting sotimeout for optimization.
+ + String comparison of If-Modified-Since headers.
+ + Touch files when expanding jars
+ + Deprecated maxReadTime.
+ + Cache directory listings.
+
+jetty-4.1.0 - 22 September 2002
+ + Fixed CGI+windows security hole.
+ + Fixed AJP13 handling of mod_jk loadbalancing.
+ + Stop servlets in opposite order to start.
+ + NCSARequest log buffered default
+ + WEB-INF/classes before WEB-INF/lib
+ + Sorted directory listings.
+ + Handle unremovable tempdir.
+ + Context Initparams to control session cookie domain, path and age.
+ + ClientCertAuthenticator protected from null subjectDN
+ + Added LimitedNCSARequestLog
+ + Use javac -target 1.2 for normal classes
+
+jetty-4.1.0RC6 - 14 September 2002
+ + Don't URL encode FileURLS.
+ + Improved HashUserRealm doco
+ + FormAuthenticator uses normal redirections now.
+ + Encode URLs of Authentication redirections.
+ + Added logon.jsp for no cookie form authentication.
+ + Extended Session API to pass request for jvmRoute handling
+ + Fixed problem with AJP 304 responses.
+ + Improved look and feel of demo
+ + Cleaned up old debug.
+ + Added redirect to welcome file option.
+
+jetty-4.1.0RC5 - 08 September 2002
+ + AJP13Listener caught up with HttpConnection changes.
+ + Added commandPrefix init param to CGI
+ + More cleanup in ThreadPool for idle death.
+ + Improved errors for misconfigured realms.
+ + Implemented security-role-ref for isUserInRole.
+
+jetty-4.1.0RC4 - 30 August 2002
+ + Included IbmJsseListener in the contrib directory.
+ + Updated jasper2 to 4.1.10 tag.
+ + Reverted to 302 for all redirections as all clients do not understand 303
+ + Created statsLock sync objects to avoid deadlock when stopping.
+
+jetty-4.1.0RC3 - 28 August 2002
+ + Fixed security problem for suffix matching with trailing "/"
+ + addWebApplications encodes paths to allow for spaces in file names.
+ + Improved handling of PUT,DELETE & MOVE.
+ + Improved handling of path encoding in Resources for bad JVMs
+ + Added buffering to request log
+ + Created and integrated the Jetty Launcher
+ + Made Resource canonicalize it's base path for directories
+ + Allow WebApplicationHandler to be used with other handlers.
+ + Added defaults descriptor to addWebApplications.
+ + Allow FORM auth pages to be within security constraint.
+
+jetty-4.1.0RC2 - 20 August 2002
+ + Conveninace setClassLoaderJava2Compliant method.
+ + Clear interrupted status in ThreadPool
+ + Fixed HttpFields cache overflow
+ + Improved ByteArrayPool to handle multiple sizes.
+ + Added HttpListener.bufferReserve
+ + Use system line separator for log files.
+ + Updated to Jasper2 (4_1_9 tag)
+ + Build ant, src and zip versions with the release
+
+jetty-4.1.0RC1 - 11 August 2002
+ + Fixed forward query string handling
+ + Fixed setCharacterEncoding to work with getReader
+ + Fixed getContext to use canonical contextPathSpec
+ + Improved the return codes for PUT
+ + Made HttpServer serializable
+ + Updated international URI doco
+ + Updated jasper to CVS snapshot 200208011920
+ + Fixed forward to jsp-file servlet
+ + Fixed handling of relative sendRedirect after forward.
+
+jetty-4.1.0RC0 - 31 July 2002
+ + Fixed getRealPath for packed war files.
+ + Changed URI default charset back to ISO_8859_1
+ + Restructured Password into Password and Credentials
+ + Added DigestAuthenticator
+ + Added link to a Jetty page in Korean.
+ + Added ExpiryHandler which can set a default Expires header.
+
+jetty-4.0.5 - 31 July 2002
+ + Fixed getRealPath for packed war files.
+ + Reversed order of ServletContextListener.contextDestroyed calls
+ + Fixed getRequestURI for RD.forward to return new URI.
+
+jetty-4.1.B1 - 19 July 2002
+ + Updated mini.http.jar target
+ + CGI Servlet, pass all HTTP headers through.
+ + CGI Servlet, catch and report program invocation failure status.
+ + CGI Servlet, fixed suffix mapping problem.
+ + CGI Servlet, set working directory for exec
+ + Support HTTP/0.9 requests again
+ + Reversed order of ServletContextListener.contextDestroyed calls
+ + Moved dynamic servlet handling to Invoker servlet.
+ + Moved webapp resource handling to Default servlet.
+ + Sessions create attribute map lazily.
+ + Added PUT,DELETE,MOVE support to webapps.
+ + Added 2.4 Filter dispatching support.
+
+jetty-3.1.9 - 15 July 2002
+ + Allow doHead requests to be forwarded.
+ + Fixed race in ThreadPool for minThreads <= CPUs
+
+jetty-4.1.B0 - 13 July 2002
+ + Added work around of JDK1.4 bug with NIO listener
+ + Moved 3rd party jars to $JETTY_HOME/ext
+ + Fixed ThreadPool bug when minThreads <= CPUs
+ + close rather than disable stream after forward
+ + Allow filter init to access servlet context methods.
+ + Keep notFoundContext out of context mapping lists.
+ + mod_jk FAQ
+ + Fixed close problem with load balancer.
+ + Stopped RD.includes closing response.
+ + RD.forward changes getRequestURI.
+ + NCSARequestLog can log to stderr
+
+jetty-4.1.D2 - 24 June 2002
+ + Support trusted external authenticators.
+ + Moved jmx classes from JettyExtra to here.
+ + Set contextloader during webapplicationcontext.start
+ + Added AJP13 listener for apache integration.
+ + Fixed ChunkableOutputStream close propagation
+ + Better recycling of HttpRequests.
+ + Protect session.getAttributeNames from concurrent modifications.
+ + Allow comma separated cookies and headers
+ + Back out Don't chunk 30x empty responses.
+ + Conditional header tested against welcome file not directory.
+ + Improved ThreadedServer stopping on bad networks
+ + Use ThreadLocals to avoid unwrapping in Dispatcher.
+
+jetty-4.0.4 - 23 June 2002
+ + Back out change: Don't chunk 30x empty responses.
+ + Conditional header tested against welcome file not directory.
+ + Improved ThreadedServer stopping on bad networks
+
+jetty-4.0.3 - 20 June 2002
+ + WebapplicationContext.start sets context loader
+ + Fixed close propagation of on-chunked output streams
+ + Force security disassociation.
+ + Better recycling of HttpRequests.
+ + Protect session.getAttributeNames from concurrent modifications.
+ + Allow session manager to be initialized when set.
+ + Fixed japanese locale
+ + Allow comma separated cookies and headers
+
+jetty-4.1.D1 - 08 June 2002
+ + Recycle servlet requests and responses
+ + Added simple buffer pool.
+ + Reworked output buffering to keep constant sized buffers.
+ + Don't chunk 30x empty responses.
+ + Fixed "" contextPaths in Dispatcher.
+ + Removed race for the starting of session scavaging
+ + Fixed /foo/../bar// bug in canonical path.
+ + Merged ResourceBase and SecurityBase into HttpContext
+
+jetty-4.0.2 - 06 June 2002
+ + Fixed web.dtd references.
+ + Fixed handler/context start order.
+ + Added OptimizeIt plug
+ + Fixed /foo/../bar// bug in canonical path.
+ + Don't chunk 30x empty responses.
+ + Fixed "" contextPaths in Dispatcher.
+ + Removed race for the starting of session scavaging
+
+jetty-3.1.8 - 06 June 2002
+ + Made SecurityConstraint.addRole() require authentication.
+ + Fixed singled threaded dynamic servlets
+ + Fixed no slash context redirection.
+ + Fixed /foo/../bar// bug in canonical path.
+
+jetty-4.1.D0 - 05 June 2002
+ + The 4.1 Series started looking for even more performance within the 2.3
+   specification.
+ + Removed the HttpMessage facade mechanism
+ + BRAND NEW WebApplicationHandler & WebApplicationContext
+ + Added TypeUtil to reduce Integer creation.
+ + General clean up of the API for for MBean getters/setters.
+ + Experimental CLIENT-CERT Authenticator
+ + Restructured ResourceHandler into ResourceBase
+ + Fixed web.dtd references.
+ + Fixed handler/context start order.
+ + Added OptimizeIt plug.
+
+jetty-4.0.1 - 22 May 2002
+ + Fixed contextclassloader on ServletContextEvents.
+ + Support graceful stopping of context and server.
+ + Fixed "null" return from getRealPath
+ + OutputStreamLogSink config improvements
+ + Updated jasper to 16 May snapshot
+
+jetty-4.0.1RC2 - 14 May 2002
+ + Better error for jre1.3 with 1.4 classes
+ + Cleaned up RD query string regeneration.
+ + 3DES Keylength was being reported as 0. Now reports 168 bits.
+ + Implemented the run-as servlet tag.
+ + Added confidential and integral redirections to HttpListener
+ + Fixed ServletResponse.reset() to resetBuffer.
+
+jetty-4.0.1RC1 - 29 April 2002
+ + Improved flushing of chunked responses
+ + Better handling if no realm configured.
+ + Expand ByteBuffer full limit with capacity.
+ + Fixed double filtering of welcome files.
+ + Fixed FORM authentication auth of login page bug.
+ + Fixed setTempDirectory creation bug
+ + Avoid flushes during RequestDispatcher.includes
+
+jetty-4.0.1RC0 - 18 April 2002
+ + Updated Jasper to CVS snapshot from Apr 18 18:50:59 BST 2002
+ + Pass pathParams via welcome file forward for jsessionid
+ + Extended facade interfaces to HttpResponse.sendError
+ + Moved basic auth handling to HttpRequest
+ + AbstractSessionManager sets contextClassLoader for scavanging
+ + Set thread context classloader for webapp load-on-startup inits
+ + Added extract arg to addWebApplications
+ + Fixed delayed response bug: Stopped HttpConnection consuming input from
+   timedout connection.
+ + DTD allows static "Get" and "Set" methods to be invoked.
+
+jetty-4.0.0 - 22 March 2002
+ + Updated tutorial configure version
+ + Added IPAddressHandler for IP restrictions
+ + Updated contributors.
+ + Minor documentation updates.
+ + Jetty.sh cygwin support
+
+jetty-4.0.RC3 - 20 March 2002
+ + Fixed ZZZ offset format to +/-HHMM
+ + Updated history
+ + JDBCUserRealm instantiates JDBC driver
+ + ContextInitialized notified before load-on-startup servlets.
+ + Suppress WriterOutputStream warning.
+ + Changed html attribute order for mozilla quirk.
+
+jetty-4.0.RC2 - 12 March 2002
+ + Fixed security constraint problem with //
+ + Fixed version for String XmlConfigurations
+ + Fixed empty referrer in NCSA log.
+ + Dont try to extract directories
+ + Added experimental nio SocketChannelListener
+ + Added skeleton load balancer
+ + Fixed column name in JDBCUserRealm
+ + Remove last of the Class.forName calls.
+ + Removed redundant sessionID check.
+ + Security FAQ
+ + Disabled the Password EXEC mechanism by default
+
+jetty-3.1.7 - 12 March 2002
+ + Fixed security problem with constraints being bypassed with // in URLs
+
+jetty-4.0.RC1 - 06 March 2002
+ + Added ContentEncodingHandler for compression.
+ + Fixed filter vs forward bug.
+ + Improved efficiency of quality list handling
+ + Simplified filter API to chunkable streams
+ + XmlParser is validating by default. use o.m.x.XmlParser.NotValidating
+   property to change.
+ + contextDestroyed event sent before destruction.
+ + Minor changes to make HttpServer work on J2ME CVM
+ + Warn if jdk 1.4 classes used on JVM <1.4
+ + WebApplication will use ContextLoader even without WEB-INF directory.
+ + FileResource depends less on FilePermissions.
+ + Call response.flushBuffer after service to flush wrappers.
+ + Empty suffix for temp directory.
+ + Contributors list as an image to prevent SPAM!
+ + Fixed recursive DEBUG loop in Logging.
+ + Updated jetty.sh to always respect arguments.
+
+jetty-3.1.6 - 28 February 2002
+ + Implemented 2.3 clarifications to security constraint semantics PLEASE
+   REVIEW YOUR SECURITY CONSTRAINTS (see README).
+ + Empty suffix for temp directory.
+ + Fixed HttpFields remove bug
+ + Set Listeners default scheme
+ + LineInput can handle any sized marks
+ + HttpResponse.sendError makes a better attempt at finding an error page.
+ + Dispatcher.forward dispatches directly to ServletHolder to avoid premature
+   exception handling.
+
+jetty-4.0.B2 - 25 February 2002
+ + Minor Jasper updates
+ + Improve handling of unknown URL protocols.
+ + Improved default jetty.xml
+ + Adjust servlet facades for welcome redirection
+ + User / mapping rather than /* for servlet requests to static content
+ + Accept jetty-web.xml or web-jetty.xml in WEB-INF
+ + Added optional JDK 1.4 src tree
+ + o.m.u.Frame uses JDK1.4 stack frame handling
+ + Added LoggerLogSink to direct Jetty Logs to JDK1.4 Log.
+ + Start ServletHandler as part of the FilterHandler start.
+ + Simplified addWebApplication
+ + Added String constructor to XmlConfiguration.
+ + Added org.mortbay.http.JDBCUserRealm
+ + Init classloader for JspServlet
+ + Slightly more agressive eating unused input from non persistent connection.
+
+jetty-4.0.B1 - 13 February 2002
+ + WriterOutputStream so JSPs can include static resources.
+ + Suppress error only for IOExceptions not derivitives.
+ + HttpConnection always eats unused bodies
+ + Merged HttpMessage and Message
+ + LineInput waits for LF after CF if seen CRLF before.
+ + Added setClassLoader and moved getFileClassPath to HttpContext
+ + Updated examples webapp from tomcat
+ + getRequestURI returns encoded path
+ + Servlet request destined for static content returns paths as default servlet
+
+jetty-4.0.B0 - 04 February 2002
+ + Implemented 2.3 security constraint semantics PLEASE REVIEW YOUR SECURITY
+   CONSTRAINTS (see README).
+ + Stop and remove NotFound context for HttpServer
+ + HttpContext destroy
+ + Release process builds JettyExtra
+ + Welcome files may be relative
+ + Fixed HttpFields remove bug
+ + Added Array element to XMLConfiguration
+ + Allow listener schemes to be set.
+ + Added index links to tutorial
+ + Renamed getHttpServers and added setAnonymous
+ + Updated crimson to 1.1.3
+ + Added hack for compat tests in watchdog for old tomcat stuff
+ + Added AbstractSessionManager
+ + Support Random Session IDs in HashSessionManager.
+ + Common handling of TRACE
+ + Updated tutorial and FAQ
+ + Reduce object count and add hash width to StringMap
+ + Factor out RolloverFileOutputStream from OutputStreamLogSink
+ + Remove request logSink and replace with RequestLog using
+   RolloverFileOutputStream
+ + Handle special characters in resource file names better.
+ + Welcome file dispatch sets requestURI.
+ + Removed triggers from Code.
+
+jetty-4.0.D4 - 14 January 2002
+ + Prevent output after forward
+ + Handle ServletRequestWrappers for Generic Servlets
+ + Improved handling of UnavailableException
+ + Extract WAR files to standard temp directory
+ + URI uses UTF8 for % encodings.
+ + Added BlueRibbon campaign.
+ + RequestDispatcher uses cached resources for include
+ + Improved HttpResponsse.sendError error page matching.
+ + Fixed noaccess auth demo.
+ + FORM auth caches UserPrincipal
+ + Added isAuthenticated to UserPrincipal
+
+jetty-4.0.D3 - 31 December 2001
+ + Fixed cached filter wrapping.
+ + Fixed getLocale again
+ + Patch jasper to 20011229101000
+ + Removed limits on mark in LineInput.
+ + Corrected name to HTTP_REFERER in CGI Servlet.
+ + Fixed UrlEncoding for % + combination.
+ + Generalized temp file handling
+ + Fixed ContextLoader lib handling.
+ + DateCache handles misses better.
+ + HttpFields uses DateCache more.
+ + Moved admin port to 8081 to avoid JBuilder
+ + Made Frame members private and fixed test harness
+ + cookies with maxAge==0 expire on 1 jan 1970
+ + setCookie always has equals
+
+jetty-3.1.5 - 11 December 2001
+ + setCookie always has equals for cookie value
+ + cookies with maxage==0 expired 1 jan 1970
+ + Fixed formatting of redirectURLs for NS4.08
+ + Fixed ChunableInputStream.resetStream bug.
+ + Ignore IO errors when trying to persist connections.
+ + Allow POSTs to static resources.
+ + stopJob/killStop in ThreadPool to improve stopping ThreadedServer on some
+   platforms.
+ + Branched at Jetty_3_1
+
+jetty-4.0.D2 - 02 December 2001
+ + Removed most of the old doco, which needs to be rewritten and added again.
+ + Restructured for demo and test hierarchies
+ + Fixed formatting of redirect URLs.
+ + Removed ForwardHandler.
+ + Removed Demo.java (until updated).
+ + Made the root context a webapplication.
+ + Moved demo docroot/servlets to demo directory
+ + added addWebApplications auto discovery
+ + Disabled last forwarding by setPath()
+ + Removed Request set methods (will be replaced)
+ + New event model to decouple from beans container.
+ + Better handling of charset in form encoding.
+ + Allow POSTs to static resources.
+ + Fixed ChunableInputStream.resetStream bug.
+ + Ignore IO errors when trying to persist connections.
+ + stopJob/killStop in ThreadPool to improve stopping ThreadedServer on some
+   platforms.
+
+jetty-4.0.D1 - 14 November 2001
+ + Fixed ServletHandler with no servlets
+ + Fixed bug with request dispatcher parameters
+ + New ContextLoader implementation.
+ + New Dispatcher implementation
+ + Added Context and Session Event Handling
+ + Added FilterHolder
+ + Added FilterHandler
+ + Changed HandlerContext to HttpContext
+ + Simplified ServletHandler
+ + Removed destroy methods
+ + Simplified MultiMap
+
+jetty-4.0.D0 - 06 November 2001
+ + Branched from Jetty_3_1 == Jetty_3_1_4
+ + 2.3 Servlet API
+ + 1.2 JSP API
+ + Jasper from tomcat4
+ + Start SessionManager abstraction.
+ + Added examples webapp from tomcat4
+ + Branched at Jetty_3_1
+
+jetty-3.1.4 - 06 November 2001
+ + Added RequestLogFormat to allow extensible request logs.
+ + Support the ZZZ timezone offset format in DateCache
+ + HTAccessHandler made stricter on misconfiguration
+ + Generate session unbind events on a context.stop()
+ + Default PathMap separator changed to ":,"
+ + PathMap now ignores paths after ; or ? characters.
+ + Remove old stuff from contrib that had been moved to extra
+ + getRealPath accepts \ URI separator on platforms using \ file separator.
+
+jetty-3.1.3 - 26 October 2001
+ + Fix security problem with trailing special characters. Trailing %00 enabled
+   JSP source to be viewed or other servlets to be bypassed.
+ + Fixed several problems with external role authentication. Role
+   authentication in JBoss was not working correctly and there were possible
+   object leaks. The fix required an API change to UserPrinciple and UserRealm.
+ + Allow a per context UserRealm instance.
+ + Upgraded JSSE to 1.0.2
+ + Improved FORM auth handling of role failure.
+ + Improved Jasper debug output.
+ + Improved ThreadedServer timeout defaults
+ + Fixed binary files in CVS
+ + Fixed Virtual hosts to case insensitive.
+ + PathMap spec separator changed from ',' to ':'. May be set with
+   org.mortbay.http.PathMap.separators system property.
+ + Correct dispatch to error pages with javax attributes set.
+
+jetty-3.1.2 - 13 October 2001
+ + Fixed double entry on PathMap.getMatches
+ + Fixed servlet handling of non session url params.
+ + Fixed attr handling in XmlParser.toString
+ + Fixed request log date formatting
+ + Fixed NotFoundHandler handling of unknown methods
+ + Fixed FORM Authentication username.
+ + Fixed authentication role handling in FORM auth.
+ + FORM authentication passes query params.
+ + Added short delay to shutdown hook for JVM bug.
+ + Added ServletHandler.sessionCount()
+ + Added run target to ant
+ + Changed 304 responses for Opera browser.
+ + Changed JSESSIONID to jsessionid
+ + Log OK state after thread low warnings.
+ + Changed unsatisfiable range warnings to debug.
+ + Further improvements in handling of shutdown.
+
+jetty-3.1.1 - 27 September 2001
+ + Fixed jar manifest format - patched 28 Sep 2001
+ + Removed JDK 1.3 dependancy
+ + Fixed ServletRequest.getLocale().
+ + Removed incorrect warning for WEB-INF/lib jar files.
+ + Handle requestdispatcher during init.
+ + Use lowercase tags in html package to be XHTML-like.
+ + Correctly ignore auth-constraint descriptions.
+ + Reduced verbosity of bad URL errors from IIS virus attacks
+
+jetty-3.1.0 - 21 September 2001
+ + Added long overdue Tutorial documentation.
+ + Improved some other documentation.
+ + Fix ResourceHandler cache invalidate.
+ + Fix ServletResponse.setLocale()
+ + Fix reuse of Resource
+ + Fix Jetty.bat for spaces.
+ + Fix .. handling in URI
+ + Fix REFFERER in CGI
+ + Fix FORM authentication on exact patterns
+ + Fix flush on stop bug in logs.
+ + Fix param reading on CGI servlet
+ + New simplified jetty.bat
+ + Improved closing of listeners.
+ + Optimized List creation
+ + Removed win32 service.exe
+ + Added HandlerContext.registerHost
+
+jetty-3.1.rc9 - 02 September 2001
+ + Added bin/orgPackage.sh script to change package names.
+ + Changed to org.mortbay domain names.
+ + Form auth login and error pages relative to context path.
+ + Fixed handling of rel form authentication URLs
+ + Added support for Nonblocking listener.
+ + Added lowResourcePersistTimeMs for more graceful degradation when we run out
+   of threads.
+ + Patched Jasper to 3.2.3.
+ + Added handlerContext.setClassPaths
+ + Fixed bug with non cookie sessions.
+ + Format cookies in HttpFields.
+
+jetty-3.1.rc8 - 22 August 2001
+ + Support WEB-INF/web-jetty.xml configuration extension for webapps
+ + Allow per context log files.
+ + Updated sponsors page
+ + Added HttpServer statistics
+ + Don't add notfound context.
+ + Many major and minor optimizations:
+ + ISO8859 conversion
+ + Buffer allocation
+ + URI pathAdd
+ + StringMap
+ + URI canonicalPath
+ + OutputStreamLogSink replaces WriterLogSink
+ + Separation of URL params in HttpHandler API.
+ + Fixed handling of default mime types
+ + Allow contextpaths without leading /
+ + Removed race from dynamic servlet initialization.
+
+jetty-3.1.rc7 - 09 August 2001
+ + Fix bug in sendRedirect for HTTP/1.1
+ + Added doco for Linux port redirection.
+ + Don't persist connections if low on threads.
+ + Added shutdown hooks to Jetty.Server to trap Ctl-C
+ + Fixed bug with session ID generation.
+ + Added FORM authentication.
+ + Remove old context path specs
+ + Added UML diagrams to Jetty architecture documentation.
+ + Use Enumerations to reduce conversions for servlet API.
+ + Optimized HttpField handling to reduce object creatiyon.
+ + ServletRequest SSL attributes in line with 2.2 and 2.3 specs.
+ + Dump Servlet displays cert chains
+ + Fixed redirect handling by the CGI Servlet.
+ + Fixed request.getPort for redirections from 80
+ + Added utility methods to ServletHandler for wrapping req/res pairs.
+ + Added method handling to HTAccessHandler.
+ + ServletResponse.sendRedirect puts URLs into absolute format.
+
+jetty-3.1.rc6 - 10 July 2001
+ + Avoid script vulnerability in error pages.
+ + Close persistent HTTP/1.0 connections on missing Content-Length
+ + Use exec for jetty.sh run
+ + Improved SSL debugging information.
+ + KeyPairTool can now load cert chains.
+ + KeyPairTool is more robust to provider setup.
+ + Fixed bug in B64Code. Optimised B64Code.
+ + Added Client authentication to the JsseListener
+ + Fixed a problem with Netscape and the acrobat plugin.
+ + Improved debug output for IOExceptions.
+ + Updated to JSSE-1.0.2, giving full strength crypto.
+ + Win32 Service uses Jetty.Server instead of HttpServer.
+ + Added getResource to HandleContext.
+ + WebApps initialize resourceBase before start.
+ + Fixed XmlParser to handle xerces1.3 OK
+ + Added Get element to the XmlConfiguration class.
+ + Added Static calls to the XmlConfiguration class.
+ + Added debug and logging config example to demo.xml
+ + Moved mime types and encodings to property bundles.
+ + RequestDispatch.forward() uses normal HandlerContext.handle() path if
+   possible.
+ + Cleaned up destroy handling of listeners and contexts.
+ + Removed getConfiguration from LifeCycleThread to avoid JMX clash.
+ + Cleaned up Win32 Service server creation.
+ + Moved gimp image files to Jetty3Extra
+
+jetty-3.1.rc5 - 01 May 2001
+ + Added build target for mini.jetty.jar - see README.
+ + Major restructing of packages to separate servlet dependancies. c.m.XML  -
+   moved XML dependant classes from c.m.Util c.m.HTTP - No servlet or XML
+   dependant classes: c.m.Jetty.Servlet - moved from c.m.HTTP.Handler.Servlet
+   c.m.Servlet - received some servlet dependant classes from HTTP.
+ + Added UnixCrypt support to c.m.U.Password
+ + Added HTaccessHandler to authenitcate against apache .htaccess files.
+ + Added query param handling to ForwardHandler
+ + Added ServletHandler().setUsingCookies().
+ + Optimized canonical path calculations.
+ + Warn and close connections if content-length is incorrectly set.
+ + Request log contains bytes actually returned.
+ + Fixed handling of empty responses at header commit.
+ + Fixed ResourceHandler handling of ;JSESSIONID
+ + Fixed forwarding to null pathInfo requests.
+ + Fixed handling of multiple cookies.
+ + Fixed EOF handling in MultiPartRequest.
+ + Fixed sync of ThreadPool idleSet.
+ + Fixed jetty.bat classpath problems.
+
+jetty-3.0.6 - 26 April 2001
+ + Fixed handling of empty responses at header commit.
+ + Fixed ResourceHandler handling of ;JSESSIONID
+ + Fixed forwarding to null pathInfo requests.
+ + Fixed EOF handlding in MultiPartRequest.
+ + Fixed sync of ThreadPool idleSet.
+ + Load-on-startup the JspServlet so that precompiled servlets work.
+
+jetty-3.1.rc4 - 14 April 2001
+ + Include full versions of JAXP and Crimson
+ + Added idle thread getter to ThreadPool.
+ + Load-on-startup the JspServlet so that precompiled servlets work.
+ + Removed stray debug println from the Frame class.
+
+jetty-3.0.5 - 14 April 2001
+ + Branched from 3.1 trunk to fix major errors
+ + Fixed LineInput bug EOF
+ + Improved flush ordering for forwarded requests.
+ + Turned off range handling by default until bugs resolved
+ + Don't chunk if content length is known.
+ + fixed getLocales handling of quality params
+ + Created better random session ID
+ + Resource handler strips URL params like JSESSION.
+ + Fixed session invalidation unbind notification to conform with spec
+ + Load-on-startup the JspServlet so that precompiled servlets work.
+
+jetty-3.1.rc3 - 09 April 2001
+ + Implemented multi-part ranges so that acrobat is happy.
+ + Simplified multipart response class.
+ + Improved flush ordering for forwarded requests.
+ + Improved ThreadPool stop handling
+ + Frame handles more JIT stacks.
+ + Cleaned up handling of exceptions thrown by servlets.
+ + Handle zero length POSTs
+ + Start session scavenger if needed.
+ + Added ContentHandler Observer to XmlParser.
+ + Allow webapp XmlParser to be observed for ejb-ref tags etc.
+ + Created better random session ID
+
+jetty-3.1.rc2 - 30 March 2001
+ + Lifecycle.start() may throw Exception
+ + Added MultiException to throw multiple nested exceptions.
+ + Improved logging of nested exceptions.
+ + Only one instance of default MIME map.
+ + Use reference JAXP1.1 for XML parsing.y
+ + Version 1.1 of configuration dtd supports New objects.
+ + Improved handling of Primitive classes in XmlConfig
+ + Renamed getConnection to getHttpConnection
+ + fixed getLocales handling of quality params
+ + fixed getParameter(name) handling for multiple values.
+ + added options to turn off ranges and chunking to support acrobat requests.
+
+jetty-3.1.rc1 - 18 March 2001
+ + Moved JMX and SASL handling to Jetty3Extra release
+ + Fixed problem with ServletContext.getContext(uri)
+ + Added Jetty documentation pages from JettyWiki
+ + Cleaned up build.xml script
+ + Minimal handling of Servlet.log before initialization.
+ + Various SSL cleanups
+ + Resource handler strips URL params like JSESSION.
+
+jetty-3.1.rc0 - 23 February 2001
+ + Added JMX management framework.
+ + Use Thread context classloader as default context loader parent.
+ + Fixed init order for unnamed servlets.
+ + Fixed session invalidation unbind notification to conform with spec
+ + Improved handling of primitives in utilities.
+ + Socket made available via HttpConnection.
+ + Improved InetAddrPort and ThreadedServer to reduce DNS lookups.
+ + Dynamic servlets may be restricted to Context classloader.
+ + Reoganized packages to allowed sealed Jars
+ + Changed getter and setter methods that did not conform to beans API.
+
+jetty-3.0.4 - 23 February 2001
+ + Fixed LineInput bug with split CRLF.
+
+jetty-3.0.3 - 03 February 2001
+ + Fixed pipelined request buffer bug.
+ + Handle empty form content without exception.
+ + Allow Log to be disabled before initialization.
+ + Included new Jetty Logo
+ + Implemented web.xml servlet mapping to a JSP
+ + Fixed handling of directories without trailing /
+
+jetty-3.0.2 - 13 January 2001
+ + Replaced ResourceHandler FIFO cache with LRU cache.
+ + Greatly improved buffering in ChunkableOutputStream
+ + Padded error bodies for IE bug.
+ + Improved HTML.Block efficiency
+ + Improved jetty.bat
+ + Improved jetty.sh
+ + Handle unknown status reasons in HttpResponse
+ + Ignore included response updates rather than IllegalStateException
+ + Removed classloading stats which were causing circular class loading
+   problems.
+ + Allow '+' in path portion of a URL.
+ + Try ISO8859_1 encoding if can't find ISO-8859-1
+ + Restructured demo site pages.
+ + Context specific security permissions.
+ + Added etc/jetty.policy as example policy file.
+
+jetty-3.0.1 - 20 December 2000
+ + Fixed value unbind notification for session invalidation.
+ + Removed double null check possibility from ServletHolder
+
+jetty-3.0.0 - 17 December 2000
+ + Improved jetty.sh logging
+ + Improved dtd resolution in XML parser.
+ + Fixed taglib parsing
+ + Fixed rel path handling in default configurations.
+ + Optional extract war files.
+ + Fixed WriterLogSink init bug
+ + Use inner class to avoid double null check sync problems
+ + Fixed rollover bug in WriterLogSink
+
+jetty-3.0.0.rc8 - 13 December 2000
+ + Optional alias checking added to FileResource.  Turned on by default on all
+   platforms without the "/" file separator.
+ + Mapped *.jsp,*.jsP,*.jSp,*.jSP,*.Jsp,*.JsP,*.JSp,*.JSP
+ + Tidied handling of ".", ".." and "//" in resource paths
+ + Protected META-INF as well as WEB-INF in web applications.
+ + Jetty.Server catches init exceptions per server
+ + getSecurityHandler creates handler at position 0.
+ + SysV unix init script
+ + Improved exit admin handling
+ + Change PathMap handling of /* to give precedence over suffix mapping.
+ + Forward to welcome pages rather than redirect.
+ + Removed special characters from source.
+ + Default log options changed if in debug mode.
+ + Removed some unused variables.
+ + Added ForwardHandler
+ + Removed security constraint on demo admin server.
+ + Patched jasper to tomcat 3.2.1
+
+jetty-3.0.0.rc7 - 02 December 2000
+ + Fixed security problem with lowercase WEB-INF uris on windows.
+ + Extended security constraints (see README and WebApp Demo).
+ + Set thread context classloader during handler start/stop calls.
+ + Don't set MIME-Version in response.
+ + Allow dynamic servlets to be served from /
+ + Handle multiple inits of same servlet class.
+ + Auto add a NotFoundHandler if needed.
+ + Added NotFoundServlet
+ + Added range handling to ResourceHandler.
+ + CGI servlet handles not found better.
+ + WEB-INF protected by NotFoundServlet rather than security constraint.
+ + PUT, MOVE disabled in WebApplication unless defaults file is passed.
+ + Conditionals apply to puts, dels and moves in ResourceHandler.
+ + URIs accept all characters < 0xff.
+ + Set the AcceptRanges header.
+ + Depreciated RollOverLogSink and moved functionality to an improved
+   WriterLogSink.
+ + Changed log options to less verbose defaults.
+ + ThreadedServer.forceStop() now makes a connection to itself to handle
+   non-premptive close.
+ + Double null lock checks use ThreadPool.__nullLockChecks.
+ + Split Debug servlet out of Admin Servlet.
+ + Added Com.mortbay.HTTP.Handler.Servlet.Context.LogSink attribute to Servlet
+   Context. If set, it is used in preference to the system log.
+
+jetty-3.0.0.rc6 - 20 November 2000
+ + RequestDispatcher.forward() only resets buffer, not headers.
+ + Added ServletWriter that can be disabled.
+ + Resource gets systemresources from it's own classloader.
+ + don't include classes in release.
+ + Allow load-on-startup with no content.
+ + Fixed RollOverFileLogSink bug with extra log files.
+ + Improved Log defaults
+ + Don't start HttpServer log sink on add.
+ + Admin servlet uses unique links for IE.
+ + Added Win32 service support
+ + Reduced risk of double null check sync problem.
+ + Don't set connection:close for normal HTTP/1.0 responses.
+ + RequestDispatcher new queries params replace old.
+ + Servlet init order may be negative.
+ + Corrected a few of the many spelling mistakes.
+ + Javadoc improvements.
+ + Webapps serve dynamics servlets by default.
+ + Warn for missing WEB-INF or web.xml
+ + Sessions try version 1 cookies in set-cookie2 header.
+ + Session cookies are given context path
+ + Put extra server and servlet info in header.
+ + Version details in header can be suppressed with System property
+   java.com.mortbay.HTTP.Version.paranoid
+ + Prevent reloading dynamic servlets at different paths.
+ + Implemented resource aliases in HandlerContext - used by Servlet Context
+ + Map tablib configuration to resource aliases.
+ + Implemented customizable error pages.
+ + Simple stats in ContextLoader.
+ + Allow HttpMessage state to be manipulated.
+ + Allow multiple set cookies.
+
+jetty-3.0.0.rc5 - 12 November 2000
+ + Default writer encoding set by mime type if not explicitly set.
+ + Relax webapp rules, accept no web.xml or no WEB-INF
+ + Pass flush through ServletOut
+ + Avoid jprobe race warnings in DateCache
+ + Allow null cookie values
+ + Servlet exceptions cause 503 unavailable rather than 500 server error
+ + RequestDispatcher can dispatch static resources.
+ + Merged DynamicHandler into ServletHandler.
+ + Added debug form to Admin servlet.
+ + Implemented servlet load ordering.
+ + Moved JSP classpath hack to ServletHolder
+ + Removed Makefile build system.
+ + Many javadoc cleanups.
+
+jetty-2.4.9 - 12 November 2000
+ + HttpListener ignore InterruptedIOExceptions
+ + HttpListener default max idle time = 20s
+ + HtmlFilter handles non default encodings
+ + Writing HttpRequests encodes path
+ + HttpRequest.write uses ISO8859_1 encoding.
+
+jetty-3.0.0.rc4 - 06 November 2000
+ + Provide default JettyIndex.properties
+ + Fixed mis-synchronization in ThreadPool.stop()
+ + Fixed mime type mapping bug introduced in RC3
+ + Ignore more IOExceptions (still visible with debug).
+
+jetty-3.0.0.rc3 - 05 November 2000
+ + Changed ThreadPool.stop for IBM 1.3 JVM
+ + Added bin/jetty.sh run script.
+ + upgraded build.xml to ant v1.2
+ + Set MaxReadTimeMs in all examples
+ + Further clean up of the connection close actions
+ + Moved unused classes from com.mortbay.Util to com.mortbay.Tools in new
+   distribution package.
+ + Handle mime suffixes containing dots.
+ + Added gz tgz tar.gz .z mime mappings.
+ + Fixed default mimemap initialization bug
+ + Optimized persistent connections by recycling objects
+ + Added HandlerContext.setHttpServerAccess for trusted contexts.
+ + Set the thread context class loader in HandlerContext.handle
+ + Prevent servlet setAttribute calls to protected context attributes.
+ + Removed redundant context attributes.
+ + Implemented mime mapping in webapplications.
+ + Strip ./ from relative resources.
+ + Added context class path dynamic servlet demo
+
+jetty-3.0.0.rc2 - 29 October 2000
+ + Replaced ISO-8859-1 literals with StringUtil static
+ + Pass file based classpath to JspServlet (see README).
+ + Prevented multiple init of ServletHolder
+ + ErlEncoding treats params without values as empty rather than null.
+ + Accept public DTD for XmlConfiguration (old style still supported).
+ + Cleaned up non persistent connection close.
+ + Accept HTTP/1. as HTTP/1.0 (for netscape bug).
+ + Fixed thread name problem in ThreadPool
+
+jetty-3.0.0.rc1 - 22 October 2000
+ + Added simple admin servlet.
+ + Added CGI to demo
+ + Added HashUserRealm and cleaned up security constraints
+ + Added Multipart request and response classes from Jetty2
+ + Moved and simplified ServletLoader to ContextLoader.
+ + Initialize JSP with classloader.
+ + All attributes in javax. java. and com.mortbay. name spaces to be set.
+ + Partial handling of 0.9 requests.
+ + removed Thread.destroy() calls.
+ + Cleaned up exception handling.
+
+jetty-2.4.8 - 23 October 2000
+ + Fixed bug with 304 replies with bodies.
+ + Improved win32 make files.
+ + Fixed closing socket problem
+
+jetty-3.0.B05 - 18 October 2000
+ + Improved null returns to get almost clean watchdog test.
+ + Cleaned up response committing and flushing
+ + Handler RFC2109 cookies (like any browser handles them!)
+ + Added default webapp servlet mapping /servlet/name/*
+ + Improved path spec interpretation by looking at 2.3 spec
+ + Implemented security-role-ref for servlets
+ + Protected servletConfig from downcast security problems
+ + Made test harnesses work with ant.
+ + improved ant documentation.
+ + Removed most deprecation warnings
+ + Fixed JarFileResource to handle jar files without directories.
+ + Implemented war file support
+ + Java2 style classloading
+ + Improved default log format for clarity.
+ + Separated context attributes and initParams.
+
+jetty-3.0.B04 - 12 October 2000
+ + Restricted context mapping to simple model for servlets.
+ + Fixed problem with session ID in paths
+ + Added modified version of JasperB3.2 for JSP
+ + Moved FileBase to docroot
+ + Merged and renamed third party jars.
+ + Do not try multiple servlets for a request.
+ + Implemented Context.getContext(uri)
+ + Added webdefault.xml for web applications.
+ + Redirect to index files, so index.jsp works.
+ + Filthy hack to teach jasper JspServer Jetty classpath
+
+jetty-3.0.B03 - 09 October 2000
+ + Expanded import package.*; lines
+ + Expanded leading tabs to spaces
+ + Improved Context to Handler contract.
+ + Parse but not handler startup ordering in web applications.
+ + Send request log via a LogSink
+ + Added append mode in RolloverFileLogSink
+ + Made LogSink a Lifecycle interface
+ + Improved handler toString
+ + Redirect context only paths.
+ + Pass object to LogSink
+ + Implemented request dispatching.
+ + Redo dynamic servlets handling
+ + Improved Log rollover.
+ + Simplified path translation and real path calculation.
+ + Catch stop and destroy exceptions in HttpServer.stop()
+ + Handle ignorable spaces in XmlConfiguration
+ + Handle ignorable spaces in WebApplication
+ + Warn about explicit sets of WebApplication
+ + Remove 411 checks as IE breaks this rule after redirect.
+ + Removed last remnants JDK 1.1 support
+ + Added release script
+
+jetty-2.4.7 - 06 October 2000
+ + Allow Objects to be passed to LogSink
+ + Set content length on errors for keep alive.
+ + Added encode methods to URI
+ + Improved win32 build
+ + fixes to SSL doco
+ + Support key and keystore passwords
+ + Various improvements to  ServletDispatch, PropertyTree and associated
+   classes.
+
+jetty-3.0.B02 - 24 August 2000
+ + Fixed LineInput bug with SSL giving CR pause LF.
+ + Fixed HTTP/1.0 input close bug
+ + Fixed bug in TestRFC2616
+ + Improved ThreadedServer stop and destroy
+ + Use resources in WebApplication
+ + Added CGI servlet
+
+jetty-3.0.B01 - 21 August 2000
+ + SSL implemented with JsseListener
+ + Partial implementation of webapp securitycontraints
+ + Implemented more webapp configuration
+ + Switched to the aelfred XML parser from microstar, which is only partially
+   validating, but small and lightweight
+
+jetty-2.4.6 - 16 August 2000
+ + Turn Linger off before closing sockets, to allow restart.
+ + JsseListener & SunJsseListener added and documented
+ + com.mortbay.Util.KeyPairTool added to handle openSSL SSL keys.
+ + Minor changes to compile with jikes.
+ + Added passive mode methods to FTP
+
+jetty-3.0.A99 - 10 August 2000
+ + Implemented jetty.xml configuration
+ + Added Xmlconfiguration utility
+ + ServletLoader simplied and uses ResourcePath
+ + Replaced FileHandler with ResourceHandler
+ + Use SAX XML parsing instead of DOM for space saving.
+ + Removed FileBase. Now use ResourceBase instead
+ + Added Resource abstraction
+ + Make it compile cleanly with jikes.
+ + Re-added commented out imports for JDK-1.1 compile
+
+jetty-3.0.A98 - 20 July 2000
+ + Implemented Jetty demos and Site as Web Application.
+ + Implemented WebApplicationContext
+ + Switched to JDK1.2 only
+ + ServletRequest.getServerPort() returns 80 rather than 0
+ + Fixed constructor to RolloverFileLogSink
+ + Improved synchronization on LogSink
+ + Allow HttpRequest.toString() handles bad requests.
+
+jetty-3.0.A97 - 13 July 2000
+ + Tempory request log implementation
+ + Less verbose debug
+ + Better tuned SocketListener parameters
+ + Started RequestDispatcher implementation.
+ + Added WML mappings
+ + Fixed makefiles for BSD ls
+ + Fixed persistent commits with no content (eg redirect+keep-alive).
+ + Implemented servlet isSecure().
+ + Implemented servlet getLocale(s).
+ + Formatted version in server info string.
+ + Protect setContentLength from a late set in default servlet HEAD handling.
+ + Added error handling to LifeCycleThread
+ + implemented removeAttribute on requests
+
+jetty-2.4.5 - 09 July 2000
+ + Don't mark a session invalid until after values unbound.
+ + Formatted version in server info.
+ + Added HtmlExpireFilter and removed response cache revention from HtmlFilter.
+ + Fixed transaction handling in JDBC wrappers
+
+jetty-3.0.A96 - 27 June 2000
+ + Fixed bug with HTTP/1.1 Head reqests to servlets.
+ + Supressed un-needed chunking EOF indicators.
+
+jetty-3.0.A95 - 24 June 2000
+ + Fixed getServletPath for default "/"
+ + Handle spaces in file names in FileHandler.
+
+jetty-3.0.A94 - 19 June 2000
+ + Implemented Sessions.
+ + PathMap exact matches can terminate with ; or # for URL sessions and
+   targets.
+ + Added HandlerContext to allow grouping of handlers into units with the same
+   file, resource and class configurations.
+ + Cleaned up commit() and added complete() to HttpResponse
+ + Updated license to clarify that commercial usage IS OK!
+
+jetty-3.0.A93 - 14 June 2000
+ + Major rethink! Moved to 2.2 servlet API
+ + Lots of changes and probably unstable
+
+jetty-3.0.A92 - 07 June 2000
+ + Added HTML classes to jar
+ + Fixed redirection bug in FileHandler
+
+jetty-2.4.4 - 03 June 2000
+ + Many debug call optimizations
+ + Added RolloverFileLogSink
+ + Improved LogSink configuration
+ + Support System.property expansions in PropertyTrees.
+ + Added uk.org.gosnell.Servlets.CgiServlet to contrib
+ + HttpRequest.setRequestPath does not null pathInfo.
+ + BasicAuthHandler uses getResourcePath so it can be used behind request
+   dispatching
+ + Added HTML.Composite.replace
+ + FileHandler implements IfModifiedSince on index files.
+ + Added build-win32.mak
+
+jetty-3.0.A91 - 03 June 2000
+ + Improved LogSink mechanism
+ + Implemented realPath and getResource methods for servlets.
+ + Abstracted ServletHandler
+ + Simplified HttpServer configuration methods and arguments
+ + Simplified class loading
+ + Added HTML classes from Jetty2
+
+jetty-3.0.A9 - 07 May 2000
+ + Improvided finally handling of output end game.
+ + Fixed double chunking bug in SocketListener.
+ + File handler checks modified headers on directory indexes.
+ + ServletLoader tries unix then platform separator for zip separator.
+
+jetty-3.0.A8 - 04 May 2000
+ + Servlet2_1 class loading re-acrchitected. See README.
+ + Moved Sevlet2_1 handler to com.mortbay.Servlet2_1
+ + addCookie takes an int maxAge rather than a expires date.
+ + Added LogSink extensible log architecture.
+ + Code.ignore only outputs when debug is verbose.
+ + Added Tenlet class for reverse telnet.
+
+jetty-2.4.3 - 04 May 2000
+ + Pass Cookies with 0 max age to browser.
+ + Allow CRLF in UrlEncoded
+
+jetty-2.4.2 - 23 April 2000
+ + Added LogSink and FileLogSink classes to allow extensible Log handling.
+ + Handle nested RequestDispatcher includes.
+ + Modified GNUJSP to prevent close in nested requests.
+ + Added GNUJSP to JettyServer.prp file.
+
+jetty-3.0.A7 - 15 April 2000
+ + Include java 1.2 source hierarchy
+ + removed excess ';' from source
+ + fixed flush problem with chunked output for IE5
+ + Added InetGateway to help debug IE5 problems
+ + added removeValue method to MultiMap
+
+jetty-2.4.1 - 09 April 2000
+ + Removed debug println from ServletHolder.
+ + Set encoding before exception in FileHandler.
+ + Fixed bug in HtmlFilter for tags split between writes.
+
+jetty-3.0.A6 - 09 April 2000
+ + Integrated skeleton 2.1 Servlet container
+ + Improved portability of Frame and Debug.
+ + Dates forced to use US locale
+ + Removed Converter utilities and InetGateway.
+ + added bin/useJava2Collections to convert to JDK1.2
+
+jetty-2.4.0 - 24 March 2000
+ + Upgraded to gnujsp 1.0.0
+ + Added per servlet resourceBase configuration.
+ + Absolute URIs are returned by getRequestURI (if sent by browser).
+ + Improved parsing of stack trace in debug mode.
+ + Implemented full handling of cookie max age.
+ + Moved SetUID native code to contrib hierarchy
+ + Form parameters only decoded for POSTs
+ + RequestDispatcher handles URI parameters
+ + Fixed bug with RequestDispatcher.include()
+ + Fixed caste problem in UrlEncoded
+ + Fixed null pointer in ThreadedServer with stopAll
+ + Added VirtualHostHandler for virtual host handling
+ + Added doc directory with a small start
+
+jetty-2.3.5 - 25 January 2000
+ + Fixed nasty bug with HTTP/1.1 redirects.
+ + ProxyHandler sends content for POSTs etc.
+ + Force locale of date formats to US.
+ + Fixed expires bug in Cookies
+ + Added configuration option to turn off Keep-Alive in HTTP/1.0
+ + Allow configured servlets to be auto reloaded.
+ + Allow properties to be configured for dynamic servlets.
+ + Added contrib/com/kiwiconsulting/jetty JSSE SSL adaptor to release.
+
+jetty-2.3.4 - 18 January 2000
+ + include from linux rather than genunix for native builds
+ + Fixed IllegalStateException handling in DefaultExceptionHandler
+ + MethodTag.invoke() is now public.
+ + Improved HtmlFilter.activate header modifications.
+ + Cookie map keyed on domain as well as name and path.
+ + DictionaryConverter handles null values.
+ + URI decodes applies URL decoding to the path.
+ + Servlet properties allow objects to be stored.
+ + Fixed interaction with resourcePaths and proxy demo.
+
+jetty-3.0.A5 - 19 October 1999
+ + Use ISO8859_1 instead of UTF8 for headers etc.
+ + Use char array in UrlEncoded.decode
+ + Do our own URL string encoding with 8859-1
+ + Replaced LF wait in LineInput with state boolean.
+
+jetty-2.3.3 - 19 October 1999
+ + Replaced UTF8 encoding with ISO-8859-1 for headers.
+ + Use UrlEncoded for form parameters.
+ + Do our own URL encoding with ISO-8859-1
+ + HTTP.HTML.EmbedUrl uses contents encoding.
+
+jetty-2.3.2 - 17 October 1999
+ + Fixed getReader bug with HttpRequest.
+ + Updated UrlEncoded with Jetty3 version.
+
+jetty-3.0.A4 - 16 October 1999
+ + Request attributes
+ + Basic Authentication Handler.
+ + Added LF wait after CR to LineInput.
+ + UTF8 in UrlDecoded.decodeString.
+
+jetty-2.3.1 - 14 October 1999
+ + Force UTF8 for FTP commands
+ + Force UTF8 for HTML
+ + Changed demo servlets to use writers in preference to outputstreams
+ + NullHandler/Server default name.name.PROPERTIES to load
+   prefix/name.name.properties
+ + Use UTF8 in HTTP headers
+ + Added Oracle DB adapter
+ + Added assert with no message to Code
+ + ThreadedServer calls setSoTimeout(_maxThreadIdleMs) on accepted sockets.
+   Idle reads will timeout.
+ + Prevented thread churn on idle server.
+ + HTTP/1.0 Keep-Alive (about time!).
+ + Fixed GNUJSP 1.0 resource bug.
+
+jetty-3.0.A3 - 14 October 1999
+ + Added LifeCycle interface to Utils implemented by ThreadPool,
+   ThreadedServer, HttpListener & HttpHandler
+ + StartAll, stopAll and destroyAll methods added to HttpServer.
+ + MaxReadTimeMs added to ThreadedServer.
+ + Added service method to HttpConnection for specialization.
+
+jetty-3.0.A2 - 13 October 1999
+ + UTF8 handling on raw output stream.
+ + Reduced flushing on writing response.
+ + Fixed LineInput problem with repeated CRs
+ + Cleaned up Util TestHarness.
+ + Prevent entity content for responses 100-199,203,304
+ + Added cookie support and demo.
+ + HTTP/1.0 Keep-alive (about time!)
+ + Virtual Hosts.
+ + NotFound Handler
+ + OPTION * Handling.
+ + TRACE handling.
+ + HEAD handling.
+
+jetty-3.0.A1 - 12 October 1999
+ + LineInput uses own buffering and uses character encodings.
+ + Added MultiMap for common handling of multiple valued parameters.
+ + Added parameters to HttpRequest
+ + Quick port of FileHandler
+ + Setup demo pages.
+ + Added PathMap implementing mapping as defined in the 2.2 API specification
+   (ie. /exact, /prefix/*, *.extention & default ).
+ + Added HttpHandler interface with start/stop/destroy lifecycle
+ + Updated HttpListener is start/stop/destroy lifecycle.
+ + Implemented simple extension architecture in HttpServer.
+
+jetty-3.0.A0 - 09 October 1999
+ + Started fresh repository in CVS
+ + Moved com.mortbay.Base classes to com.mortbay.Util
+ + Cleanup of UrlEncoded, using 1.2 Collections.
+ + Cleanup of URI, using 1.2 Collections.
+ + Extended URI to handle absolute URLs
+ + Cleanup of LineInput, using 1.2 Collections.
+ + Moved HttpInput/OutputStream to ChunkableInput/OutputStream.
+ + Cleaned up chunking code to use LineInput and reduce buffering.
+ + Added support for transfer and content encoding filters.
+ + Added support for servlet 2.2 outbut buffer control.
+ + Generalized notification of outputStream events.
+ + Split HttpHeader into HttpFields and HttpMessage.
+ + HttpMessage supports chunked trailers.
+ + HttpMessage supports message states.
+ + Added generalized HTTP Connection.
+ + Cleanup of HttpRequest and decoupled from Servlet API
+ + Cleanup and abstraction of ThreadPool.
+ + ThreadedServer based on ThreadPool.
+ + Cleanup of HttpResponse and decoupled from Servlet API
+ + Created RFC2616 test harness.
+ + gzip and deflate request transfer encodings
+ + TE field coding and trailer handler
+ + HttpExceptions now produce error pages with specific detail of the
+   exception.
+
+jetty-2.3.0 - 05 October 1999
+ + Added SetUID class with native Unix call to set the effective User ID.
+ + FTP closes files after put/get.
+ + FTP uses InetAddress of command socket for data socket.
+
+jetty-2.3.0A - 22 September 1999
+ + Added GNUJSP 1.0 for the JSP 1.0 API.
+ + Use javax.servlet classes from JWSDK1.0
+ + Added "Powered by Jetty" button.
+ + ServerContext available to HtmlFilters via context param
+ + Made session IDs less predictable and removed race.
+ + Added BuildJetty.java file.
+ + Expanded tabs to spaces in source.
+
+jetty-2.2.8 - 15 September 1999
+ + Fixed bug in Element.attribute with empty string values.
+ + Made translation of getRequestURI() optional.
+ + Removed recursion from TranslationHandler
+ + Added disableLog() to turn off logging.
+ + Allow default table attributes to be overriden.
+ + Improved quoting in HTML element values
+
+jetty-2.2.7 - 09 September 1999
+ + Reverted semantics of getRequestURI() to return untranslated URI.
+ + Added GzipFilter for content encoding.
+ + Added default row, head and cell elements to Table.
+ + FileHandler passes POST request through if the file does not exist.
+
+jetty-2.2.6 - 05 September 1999
+ + New implementation of ThreadPool, avoids a thread leak problem.
+ + Fixed Cookie max age order of magnitude bug.
+ + Cookies always available from getCookies.
+ + Cookies parameter renamed to CookiesAsParameters
+ + HttpRequest.getSession() always returns a session as per the latest API
+   spec.
+ + Added destroy() method on all HttpHandlers.
+ + ServletHandler.destroy destroys all servlets.
+ + FileHandler does not server files ending in '/'
+ + Ignore duplicate single valued headers, rather than reply with bad request,
+   as IE4 breaks the rules.
+ + Allow the handling of getPathTranslated to be configured in ServletHandler.
+ + Removed JRUN options from ServletHandler configuration.
+ + Added ServletRunnerHandler to the contrib directories.
+ + Updated HTML package to better support CSS:
+ + cssClass, cssID and style methods added to element.
+ + SPAN added to Block
+ + media added to Style
+ + class StyleLink added.
+
+jetty-2.2.5 - 19 August 1999
+ + Fixed bug with closing connections in ThreadedServer
+ + Made start and stop non final in ThreadedServer
+ + Better default handling of ServletExceptions
+ + Always close connection after a bad request.
+ + Set Expires header in HtmlFilter.
+ + Don't override the cookie as parameter option.
+ + Limited growth in MultiPartResponse boundary.
+ + Improved error messages from Jetty.Server.
+ + Close loaded class files so Win32 can overwrite them before GC (what a silly
+   file system!).
+
+jetty-2.2.4 - 02 August 1999
+ + ThreadedServer can use subclasses of Thread.
+ + Better help on Jetty.Server
+ + HttpRequests may be passed to HttpFilter constructors.
+ + HtmlFilter blanks IfModifiedSince headers on construction
+ + Fixed bugs in HtmlFilter parser and added TestHarness.
+ + Improved cfg RCS script.
+
+jetty-2.2.3 - 27 July 1999
+ + Fixed parser bug in HtmlFilter
+ + Made setInitialize public in ServletHolder
+ + Improved performance of com.mortbay.HTML.Heading
+ + Added stop call to HttpServer, used by Exit Servlet.
+ + Simplified JDBC connection handling so that it works with Java1.2 - albeit
+   less efficiently.
+ + FileHandler defaults to allowing directory access.
+ + JDBC tests modified to use cloudscape as DB.
+
+jetty-2.2.2 - 22 July 1999
+ + Fixed bug in HtmlFilter that prevented single char buffers from being
+   written.
+ + Implemented getResourceAsStream in FileJarServletLoader
+ + Fixed bug with CLASSPATH in FileJarServletLoader after attempt to load from
+   a jar.
+ + Fixed bug in com.mortbay.Util.IO with thread routines.
+ + Moved more test harnesses out of classes.
+ + File handler passes through not allowed options for non existant files.
+ + NotFoundHandler can repond with SC_METHOD_NOT_ALLOWED.
+ + Improved com.mortbay.Base.Log handling of different JVMs
+ + Minor fixes to README
+
+jetty-2.2.1 - 18 July 1999
+ + Comma separate header fields.
+ + Protect against duplicate single valued headers.
+ + Less verbose debug in PropertyTree
+ + Ignore IOException in ThreadedServer.run() when closing.
+ + Limit maximum line length in HttpInputStream.
+ + Response with SC_BAD_REQUEST rather than close in more circumstances
+ + Handle continuation lines in HttpHeader.
+ + HtmlFilter resets last-modified and content-length headers.
+ + Implemented com.mortbay.Util.IO as a ThreadPool
+ + Decoupled ExceptionHandler configuration from Handler stacks. Old config
+   style will produce warning and Default behavior. See new config file format
+   for changes.
+ + Added TerseExceptionHandler
+ + Added optional resourceBase property to HttpConfiguration. This is used as a
+   URL prefix in the getResource API and was suggested by the JSERV and Tomcat
+   implementors.
+
+jetty-2.2.0 - 01 July 1999
+ + Improved feature description page.
+ + Added Protekt SSL HttpListener
+ + Moved GNUJSP and Protekt listener to a contrib hierarchy.
+ + ThreadedServer.stop() closes socket before interrupting threads.
+ + Exit servlet improved (a little).
+ + Fixed some of the javadoc formatting.
+
+jetty-2.2.Beta4 - 29 June 1999
+ + FileHandler flushes files from cache in DELETE method.
+ + ThreadedServer.stop() now waits until all threads are stopped.
+ + Options "allowDir" added to FileHandler.
+ + Added getGlobalProperty to Jetty.Server and used this to configure default
+   page type.
+ + Updated README.txt
+ + Restructured com.mortbay.Jetty.Server for better clarity and documentation.
+ + Added comments to configuration files.
+ + Made ServerSocket and accept call generic in ThreadedServer for SSL
+   listeners.
+ + Altered meaning of * in PropertyTree to assist in abbreviated configuration
+   files.
+ + Added JettyMinimalDemo.prp as an example of an abbreviated configuration.
+ + Expanded Mime.prp file
+ + Added property handling to ServletHandler to read JRUN servlet configuration
+   files.
+
+jetty-2.2.Beta3 - 22 June 1999
+ + Re-implemented ThreadedServer to improve and balance performance.
+ + Added file cache to FileHandler
+ + Implemented efficient version of ServletContext.getResourceAsStream() that
+   does not open a new socket connection (as does getResource()).
+ + LookAndFeelServlet uses getResourceAsStream to get the file to wrap. This
+   allows it to benefit from any caching done and to wrap arbitrary content
+   (not just files).
+ + Restructure demo so that LookAndFeel content comes from simple handler
+   stack.
+ + Fixed file and socket leaks in Include and Embed tags.
+ + Ran dos2unix on all text files
+ + Applied contributed patch of spelling and typo corrections
+ + Added alternate constructors to HTML.Include for InputStream.
+ + Server.shutdown() clears configuration so that server may be restarted in
+   same virtual machine.
+ + Improved Block.write.
+ + Fixed bug in HttpResponse flush.
+
+jetty-2.2.Beta2 - 12 June 1999
+ + Added all write methods to HttpOutputStream$SwitchOutputStream
+ + Added com.mortbay.Jetty.Server.shutdown() for gentler shutdown of server.
+   Called from Exit servlet
+ + HttpRequest.getParameterNames() no longer alters the order returned by
+   getQueryString().
+ + Handle  path info of a dynamic loaded servlets and correctly set the servlet
+   path.
+ + Standardized date format in persistent cookies.
+
+jetty-2.2.Beta1 - 07 June 1999
+ + Defined abstract ServletLoader, derivations of which can be specified in
+   HttpConfiguration properties.
+ + Implemented all HttpServer attribute methods by mapping to the
+   HttpConfiguration properties.  Dynamic reconfiguration is NOT supported by
+   these methods (but we are thinking about it).
+ + Close files after use to avoid "file leak" under heavy load.
+ + Fixed missing copyright messages from some contributions
+ + Fixed incorrect version numbers in a few places.
+ + Improved ThreadPool synchronization and added minThreads.
+ + Allow configuration of MinListenerThreads, MaxListenerThreads,
+   MaxListenerThreadIdleMs
+ + HtmlFilter optimized for being called by a buffered writer.
+ + Don't warn about IOExceptions unless Debug is on.
+ + Limit the job queue only grow to the max number of threads.
+ + Included GNUJSP 0.9.9
+ + Optional use of DateCache in log file format
+ + Fixed cache in FileJarServletLoader
+ + Destroy requests and responses to help garbage collector.
+ + Restructure ThreadedServer to reduce object creation.
+
+jetty-2.2.Beta0 - 31 May 1999
+ + Servlet loader handles jar files with different files separator.
+ + ThreadedServer gently shuts down.
+ + Handle malformed % characters in URLs.
+ + Included and improved version of ThreadPool for significant performance
+   improvement under high load.
+ + HttpRequest.getCookies returns empty array rather than null for no cookies.
+ + Added HttpResponse.requestHandled() method to avoid bug with servlet doHead
+   method.
+ + Added Page.rewind() method to allow a page to be written multiple times
+ + Added "Initialize" attribute to servlet configuration to allow servlet to be
+   initialized when loaded.
+ + LogHandler changed to support only a single outfile and optional append.
+ + Included contributed com.mortbay.Jetty.StressTester class
+ + Token effort to keep test files out of the jar
+ + Removed support for STF
+
+jetty-2.2.Alpha1 - 07 May 1999
+ + ServletHolder can auto reload servlets
+ + Dynamic servlets can have autoReload configured
+ + Wait for requests to complete before reloading.
+ + Call destroy on old servlets when reloading.
+ + Made capitalization of config file more consistent(ish)
+ + Fixed bug in SessionDump
+
+jetty-2.2.Alpha0 - 06 May 1999
+ + Improved PropertyTree implementation
+ + Old Jetty.Server class renamed to Jetty.Server21
+ + New Server class using PropertyTree for configuration
+ + HttpHandlers given setProperties method to configure via Properties.
+ + HttpListener class can be configured
+ + Mime suffix mapping can be configured.
+ + Removed historic API from sessions
+ + Improved SessionDump servlet
+ + Fixed date overflow in Cookies
+ + HttpResponse.sendError avoids IllegalStateException
+ + Added ServletLoader implementation if ClassLoader.
+ + Dynamic loading of servlets.
+ + Added reload method to ServletHolder, but no way to call it yet.
+ + Changed options for FileServer
+ + Implemented ServletServer
+ + Removed SimpleServletServer
+
+jetty-2.1.7 - 22 April 1999
+ + Fixed showstopper bug with getReader and getWriter in requests and
+   responses.
+ + HttpFilter uses package interface to get HttpOutputStream
+
+jetty-2.1.6 - 21 April 1999
+ + Reduced initial size of most hashtables to reduce default memory overheads.
+ + Throw IllegalStateException as required from gets of
+   input/output/reader/writer in requests/responses.
+ + New simpler version of PropertyTree
+ + Updated PropertyTreeEditor
+ + Return EOF from HttpInputStream that has a content length.
+ + Added additional date formats for HttpHeader.getDateHeader
+
+jetty-2.1.5 - 15 April 1999
+ + Session URL encoding fixed for relative URLs.
+ + Reduced session memory overhead of sessions
+ + Form parameters protected against multiple decodes when redirected.
+ + Added setType methods to com.mortbay.FTP.Ftp
+ + Fixed bugs with invalid sessions
+ + Page factory requires response for session encoding
+ + Moved SessionHandler to front of stacks
+ + HtmlFilter now expands <!=SESSION> to the URL encoded session if required.
+ + Instrumented most of the demo to support URL session encoding.
+ + Implemented HttpRequest.getReader()
+ + Servlet log has been diverted to com.mortbay.Base.Log.event() Thus debug
+   does not need to be turned on to see servlet logs.
+ + Fixed alignment bug in TableForm
+ + Removed RFCs from package
+ + Fixed bug in ServletDispatch for null pathInfo
+
+jetty-2.1.4 - 26 March 1999
+ + Fixed problem compiling PathMap under some JDKs.
+ + Reduced HTML dependence in HTTP package to allow minimal configuration
+ + Tightened license agreement so that binary distributions are required to
+   include the license file.
+ + HttpRequest attributes implemented.
+ + Session max idle time implemented.
+ + pathInfo returns null for zero length pathInfo (as per spec). Sorry if this
+   breaks your servlets - it is a pain!
+ + fixed bug in getRealPath
+ + getPathTranslated now call getRealPath with pathInfo (as per spec).
+
+jetty-2.1.3 - 19 March 1999
+ + Added support for suffixes to PathMap
+ + Included GNUJSP implementation of Java Server Pages
+ + Use Java2 javadoc
+
+jetty-2.1.2 - 09 March 1999
+ + JSDK 2.1.1
+ + API documentation for JSDK 2.1.1
+ + Cascading style sheet HTML element added.
+ + Fixed trailing / bug in FileHandler (again!).
+ + Converted most servlets to HttpServlets using do Methods.
+
+jetty-2.1.1 - 05 March 1999
+ + Reduced number of calls to getRemoteHost for optimization
+ + Faster version of HttpInputStream.readLine().
+ + com.mortbay.Base.DateCache class added and used to speed date handling.
+ + Handle '.' in configured paths (temp fix until PropertyTrees)
+ + Fast char buffer handling in HttpInputStream
+ + Faster version of HttpHeader.read()
+ + Faster version of HttpRequest
+ + Size all StringBuffers
+
+jetty-2.1.0 - 22 February 1999
+ + Session URL Encoding
+ + PropertyTrees (see new Demo page)
+ + ServletDispatch (see new Demo page)
+ + image/jpg -> image/jpeg
+ + Deprecated com.mortbay.Util.STF
+ + getServlet methods return null.
+
+jetty-2.1.B1 - 13 February 1999
+ + Fixed bug with if-modified-since in FileHandler
+ + Added video/quicktime to default MIME types.
+ + Fixed bug with MultipartRequest.
+ + Updated DefaultExceptionHandler.
+ + Updated InetAddrPort.
+ + Updated URI.
+ + Implemented Handler translations and getRealPath.
+ + Improved handling of File.separator in FileHandler.
+ + Implemented RequestDispatcher (NOT Tested!).
+ + Implemented getResource and getResourceAsStream (NOT Tested!).
+ + Replace package com.mortbay.Util.Gateway with class
+   com.mortbay.Util.InetGateway
+
+jetty-2.1.B0 - 30 January 1999
+ + Uses JSDK2.1 API, but not all methods implemented.
+ + Added support for PUT, MOVE, DELETE in FileHandler
+ + FileHandler now sets content length.
+ + Added plug gateway classes com.mortbay.Util.Gateway
+ + Fixed command line bug with SimpleServletConfig
+ + Minor changes to support MS J++ and its non standard language extensions -
+   MMMmmm should have left it unchanged!
+
+jetty-2.0.5 - 15 December 1998
+ + Temp fix to getCharacterEncoding
+ + added getHeaderNoParams
+
+jetty-2.0.4 - 10 December 1998
+ + Use real release of JSDK2.0 (rather than beta).
+ + Portability issues solved for Apple's
+ + Improved error code returns
+ + Removed MORTBAY_HOME support from Makefiles
+ + Improved default Makefile behaviour
+ + Implement getCharacterEncoding
+
+jetty-2.0.3 - 13 November 1998
+ + Limit threads in ThreadedServer and low priority listener option greatly
+   improve performance under worse case loads.
+ + Fix bug with index files for Jetty.Server. Previously servers configured
+   with com.mortbay.Jetty.Server would not handle index.html files.  Need to
+   make this configurable in the prp file.
+ + Fixed errors in README file: com.mortbay.Jetty.Server was called
+   com.mortbay.HTTP.Server
+
+jetty-2.0.2 - 01 November 1998
+ + Use JETTY_HOME rather than MORTBAY_HOME for build environment
+ + Add thread pool to threaded server for significant performance improvement.
+ + Buffer files during configuration
+ + Buffer HTTP Response headers.
+
+jetty-2.0.1 - 27 October 1998
+ + Released under an Open Source license.
+
+jetty-2.0.0 - 25 October 1998
+ + Removed exceptional case from FileHandler redirect.
+ + Removed Chat demo (too many netscape dependencies).
+ + Fixed Code.formatObject handling of null objects.
+ + Added multipart/form-data demo.
+
+jetty-2.0.Beta3 - 29 September 1998
+ + Send 301 for directories without trailing / in FileHandler
+ + Ignore exception from HttpListener
+ + Properly implemented multiple listening addresses
+ + Added com.mortbay.Jetty.Server (see README.Jetty)
+ + Demo converted to an instance of com.mortbay.Jetty.Server
+ + Fixed Log Handler again.
+ + Added com.mortbay.HTTP.MultiPartRequest to handle file uploads
+
+jetty-2.0Beta2 - 01 July 1998
+ + Fixed Log Handler for HTTP/1.1
+ + Slight improvement in READMEEs
+
+jetty-2.0Beta1 - 01 June 1998
+ + Improved performance of Code.debug() calls, significantly in the case of non
+   matching debug patterns.
+ + Fixed bug with calls to service during initialization of servlet
+ + Provided addSection on com.mortbay.HTML.Page
+ + Provided reset on com.mortbay.HTML.Composite.
+ + Proxy demo in different server instance
+ + Handle full URLs in HTTP requests (to some extent)
+ + Improved performance with special asciiToLowerCase
+ + Warn if MSIE used for multi part MIME.
+
+jetty-2.0Alpha2 - 01 May 1998
+ + JDK1.2 javax.servlet API
+ + Added date format to Log
+ + Added timezone to Log
+ + Handle params in getIntHeader and getDateHeader
+ + Removed HttpRequest.getByteContent
+ + Use javax.servlet.http.HttpUtils.parsePostData
+ + Use javax.servlet.http.Cookie
+ + Use javax.servlet.http.HttpSession
+ + Handle Single Threaded servlets with servlet pool
+
+jetty-1.3.5 - 01 May 1998
+ + Fixed socket inet bug in FTP
+ + Debug triggers added to com.mortbay.Base.Code
+ + Added date format to Log
+ + Correct handling of multiple parameters
+
+jetty-2.0Alpha1 - 08 April 1998
+ + Fixed forward bug with no port number
+ + Removed HttpRequestHeader class
+ + Debug triggers added to com.mortbay.Base.Code
+ + Handle HTTP/1.1 Host: header
+ + Correct formatting of Date HTTP headers
+ + HttpTests test harness
+ + Add HTTP/1.1 Date: header
+ + Handle file requests with If-Modified-Since: or If-Unmodified-Since:
+ + Handle HEAD properly
+ + Send Connection: close
+ + Requires Host: header for 1.1 requests
+ + Sends chunked data for 1.1 responses of unknown length.
+ + handle extra spaces in HTTP headers
+ + Really fixed handling of multiple parameters
+ + accept chunked data
+ + Send 100 Continue for HTTP/1.1 requests (concerned about push???)
+ + persistent connections
+
+jetty-1.3.4 - 15 March 1998
+ + Fixed handling of multiple parameters in query and form content.
+   "?A=1%2C2&A=C%2CD" now returns two values ("1,2" & "C,D") rather than 4.
+ + ServletHandler now takes an optional file base directory name which is used
+   to set the translated path for pathInfo in servlet requests.
+ + Dump servlet enhanced to exercise these changes.
+
+jetty-1.3.3
+ + Fixed TableForm.addButtonArea bug.
+ + TableForm.extendRow() uses existing cell
+ + Closed exception window in HttpListener.java
+
+jetty-1.3.2
+ + Fixed proxy bug with no port number
+ + Added per Table cell composite factories
+
+jetty-1.3.1
+ + Minor fixes in SmtpMail
+ + ForwardHandler only forwards as http/1.0 (from Tobias.Miller)
+ + Improved parsing of stack traces
+ + Better handling of InvocationTargetException in debug
+ + Minor release adjustments for Tracker
+
+jetty-1.3.0
+ + Added DbAdaptor to JDBC wrappers
+ + Beta release of Tracker
+
+jetty-1.2.0
+ + Reintroduced STF
+ + Fixed install bug for nested classes
+ + Better Debug configuration
+ + DebugServlet
+ + Alternate look and feel for Jetty
+
+jetty-1.1.1
+ + Improved documentation
+
+jetty-1.1
+ + Improved connection caching in java.mortbay.JDBC
+ + Moved HttpCode to com.mortbay.Util
+
+jetty-1.0.1
+ + Bug fixes
+
+jetty-1.0
+ + First release in com.mortbay package structure
+ + Included Util, JDBC, HTML, HTTP, Jetty
+
diff --git a/lib/javax.servlet-3.0.0.v201112011016-sources.jar b/lib/javax.servlet-3.0.0.v201112011016-sources.jar
new file mode 100644
index 0000000..2baa97b
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016-sources.jar
Binary files differ
diff --git a/lib/javax.servlet-3.0.0.v201112011016-sources.jar.sha1 b/lib/javax.servlet-3.0.0.v201112011016-sources.jar.sha1
new file mode 100644
index 0000000..aea41c3
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016-sources.jar.sha1
@@ -0,0 +1 @@
+15c914135cf30177b83c321250c458396289cd37
\ No newline at end of file
diff --git a/lib/javax.servlet-3.0.0.v201112011016.jar b/lib/javax.servlet-3.0.0.v201112011016.jar
new file mode 100644
index 0000000..b135409
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016.jar
Binary files differ
diff --git a/lib/javax.servlet-3.0.0.v201112011016.jar.sha1 b/lib/javax.servlet-3.0.0.v201112011016.jar.sha1
new file mode 100644
index 0000000..2603fb9
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016.jar.sha1
@@ -0,0 +1 @@
+0aaaa85845fb5c59da00193f06b8e5278d8bf3f8
\ No newline at end of file
diff --git a/lib/javax.servlet-3.0.0.v201112011016.pom b/lib/javax.servlet-3.0.0.v201112011016.pom
new file mode 100644
index 0000000..2bcfad6
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016.pom
@@ -0,0 +1,17 @@
+<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/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.eclipse.jetty.orbit</groupId>
+    <artifactId>jetty-orbit</artifactId>
+    <version>1</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.eclipse.jetty.orbit</groupId>
+  <artifactId>javax.servlet</artifactId>
+  <version>3.0.0.v201112011016</version>
+  <name>Jetty Orbit :: Servlet API</name>
+  <description>
+    This artifact originates from the Orbit Project at Eclipse, 
+    it is an osgi bundle and is signed as well.
+  </description>
+  <packaging>orbit</packaging>
+</project>
diff --git a/lib/javax.servlet-3.0.0.v201112011016.pom.sha1 b/lib/javax.servlet-3.0.0.v201112011016.pom.sha1
new file mode 100644
index 0000000..787a813
--- /dev/null
+++ b/lib/javax.servlet-3.0.0.v201112011016.pom.sha1
@@ -0,0 +1 @@
+b23491173f52c804489f6625a661f4ba946f4e51
\ No newline at end of file
diff --git a/lib/jetty-util-6.1.26.jar b/lib/jetty-util-6.1.26.jar
new file mode 100644
index 0000000..cd23752
--- /dev/null
+++ b/lib/jetty-util-6.1.26.jar
Binary files differ
diff --git a/lib/jetty-util-6.1.26.jar.sha1 b/lib/jetty-util-6.1.26.jar.sha1
new file mode 100644
index 0000000..e00b3a8
--- /dev/null
+++ b/lib/jetty-util-6.1.26.jar.sha1
@@ -0,0 +1 @@
+e5642fe0399814e1687d55a3862aa5a3417226a9
\ No newline at end of file
diff --git a/lib/jetty-util-6.1.26.pom b/lib/jetty-util-6.1.26.pom
new file mode 100644
index 0000000..fb52f3b
--- /dev/null
+++ b/lib/jetty-util-6.1.26.pom
@@ -0,0 +1,111 @@
+<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/maven-v4_0_0.xsd">
+  <parent>
+    <artifactId>project</artifactId>
+    <groupId>org.mortbay.jetty</groupId>
+    <version>6.1.26</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.mortbay.jetty</groupId>
+  <artifactId>jetty-util</artifactId>
+  <name>Jetty Utilities</name>
+  <description>Utility classes for Jetty</description>
+  <build>
+    <testResources>
+      <testResource>
+        <directory>src/test/java</directory>
+        <includes>
+          <include>**/*Test.java</include>
+          <include>org/mortbay/**/*.xml</include>
+        </includes>
+        <excludes>
+          <exclude>**/Abstract*.java</exclude>
+        </excludes>
+      </testResource>
+    </testResources>
+    <plugins>
+      <plugin>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>clean</id>
+            <phase>clean</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <tasks>
+                <delete failonerror="false" file="../../lib/${project.artifactId}-${project.version}.${project.packaging}" />
+                <delete failonerror="false" file="../../etc/jetty-logging.xml" />
+              </tasks>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copyjar</id>
+            <phase>install</phase>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <configuration>
+              <tasks>
+                <copy failonerror="false" file="target/${project.artifactId}-${project.version}.${project.packaging}" todir="../../lib/" />
+                <copy failonerror="false" file="src/main/config/etc/jetty-logging.xml" todir="../../etc" />
+              </tasks>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>${maven-bundle-plugin-version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+            <configuration>
+              <instructions>
+                <Bundle-SymbolicName>org.mortbay.jetty.util</Bundle-SymbolicName>
+                <Bundle-RequiredExecutionEnvironment>J2SE-1.4</Bundle-RequiredExecutionEnvironment>
+                <Import-Package>!org.mortbay.*,org.slf4j;resolution:=optional,*</Import-Package>
+                <Bundle-DocURL>http://jetty.mortbay.org</Bundle-DocURL>
+              </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>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mortbay.jetty</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>${servlet-version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/lib/jetty-util-6.1.26.pom.sha1 b/lib/jetty-util-6.1.26.pom.sha1
new file mode 100644
index 0000000..c4edc57
--- /dev/null
+++ b/lib/jetty-util-6.1.26.pom.sha1
@@ -0,0 +1 @@
+392748d8f7f97d4b37576ac710fceffa58ba294e
\ No newline at end of file
diff --git a/lib/slf4j-api-1.6.1.jar b/lib/slf4j-api-1.6.1.jar
new file mode 100644
index 0000000..f1f4fdd
--- /dev/null
+++ b/lib/slf4j-api-1.6.1.jar
Binary files differ
diff --git a/lib/slf4j-api-1.6.1.jar.sha1 b/lib/slf4j-api-1.6.1.jar.sha1
new file mode 100644
index 0000000..3f1a76e
--- /dev/null
+++ b/lib/slf4j-api-1.6.1.jar.sha1
@@ -0,0 +1 @@
+6f3b8a24bf970f17289b234284c94f43eb42f0e4
\ No newline at end of file
diff --git a/lib/slf4j-api-1.6.1.pom b/lib/slf4j-api-1.6.1.pom
new file mode 100644
index 0000000..e7e09ff
--- /dev/null
+++ b/lib/slf4j-api-1.6.1.pom
@@ -0,0 +1,101 @@
+<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/maven-v4_0_0.xsd">

+

+  <parent>

+    <groupId>org.slf4j</groupId>

+    <artifactId>slf4j-parent</artifactId>

+    <version>1.6.1</version>

+  </parent>

+

+  <modelVersion>4.0.0</modelVersion>

+

+  <groupId>org.slf4j</groupId>

+  <artifactId>slf4j-api</artifactId>

+  <packaging>jar</packaging>

+  <name>SLF4J API Module</name>

+

+  <url>http://www.slf4j.org</url>

+  <description>The slf4j API</description>

+

+  <dependencies>

+

+  </dependencies> 

+

+  <build>

+    <plugins>

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-surefire-plugin</artifactId>

+        <configuration>

+          <forkMode>once</forkMode>

+          <reportFormat>plain</reportFormat>

+          <trimStackTrace>false</trimStackTrace>

+          <excludes>

+            <exclude>**/AllTest.java</exclude>

+            <exclude>**/PackageTest.java</exclude>

+          </excludes>

+        </configuration>

+      </plugin>

+

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-jar-plugin</artifactId>

+        <configuration>

+          <archive>

+            <manifestEntries>

+              <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>

+              <Bundle-Description>${project.description}</Bundle-Description>

+              <Implementation-Version>${project.version}</Implementation-Version>

+            </manifestEntries>

+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>

+          </archive>

+        </configuration>

+        <executions>

+          <execution>

+            <id>bundle-test-jar</id>

+            <phase>package</phase>

+            <goals>

+              <goal>jar</goal>

+              <goal>test-jar</goal>

+            </goals>

+          </execution>

+        </executions>

+      </plugin>

+

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-antrun-plugin</artifactId>

+        <executions>

+          <execution>

+            <phase>process-classes</phase>

+            <goals>

+             <goal>run</goal>

+            </goals>

+          </execution>

+        </executions>

+        <configuration>

+          <tasks>

+            <echo>Removing slf4j-api's dummy StaticLoggerBinder and StaticMarkerBinder</echo>

+            <delete dir="target/classes/org/slf4j/impl"/>

+          </tasks>

+        </configuration>

+      </plugin>

+

+    </plugins>

+

+  </build>

+  

+  <reporting>

+    <plugins>

+      <plugin>

+        <groupId>org.codehaus.mojo</groupId>

+        <artifactId>clirr-maven-plugin</artifactId>

+        <configuration>

+          <comparisonVersion>1.5.6</comparisonVersion>

+        </configuration>

+      </plugin>

+    </plugins>

+  </reporting>

+

+</project>
\ No newline at end of file
diff --git a/lib/slf4j-api-1.6.1.pom.sha1 b/lib/slf4j-api-1.6.1.pom.sha1
new file mode 100644
index 0000000..2ff79af
--- /dev/null
+++ b/lib/slf4j-api-1.6.1.pom.sha1
@@ -0,0 +1 @@
+bd86aca3b3779fa5aa5e4442a5db2ebf4035365c
\ No newline at end of file
diff --git a/lib/slf4j-jdk14-1.6.1.jar b/lib/slf4j-jdk14-1.6.1.jar
new file mode 100644
index 0000000..6eb6367
--- /dev/null
+++ b/lib/slf4j-jdk14-1.6.1.jar
Binary files differ
diff --git a/lib/slf4j-jdk14-1.6.1.jar.sha1 b/lib/slf4j-jdk14-1.6.1.jar.sha1
new file mode 100644
index 0000000..2e18640
--- /dev/null
+++ b/lib/slf4j-jdk14-1.6.1.jar.sha1
@@ -0,0 +1 @@
+251899d8c17e29ed4c53d98c88e54241a14d9591
\ No newline at end of file
diff --git a/lib/slf4j-jdk14-1.6.1.pom b/lib/slf4j-jdk14-1.6.1.pom
new file mode 100644
index 0000000..50df72b
--- /dev/null
+++ b/lib/slf4j-jdk14-1.6.1.pom
@@ -0,0 +1,69 @@
+<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/maven-v4_0_0.xsd">

+

+	<parent>

+		<groupId>org.slf4j</groupId>

+		<artifactId>slf4j-parent</artifactId>

+    	<version>1.6.1</version>

+	</parent>

+	

+	<modelVersion>4.0.0</modelVersion>

+

+	<groupId>org.slf4j</groupId>

+	<artifactId>slf4j-jdk14</artifactId>

+

+	<packaging>jar</packaging>

+	<name>SLF4J JDK14 Binding</name>

+

+	<url>http://www.slf4j.org</url>

+	<description>

+		The slf4j JDK14 binding

+	</description>

+

+  <dependencies>

+	  <dependency>

+      <groupId>org.slf4j</groupId>

+      <artifactId>slf4j-api</artifactId>

+		</dependency>

+

+    <dependency>

+      <groupId>org.slf4j</groupId>

+      <artifactId>slf4j-api</artifactId>

+      <type>test-jar</type>

+      <version>${project.version}</version>

+      <scope>test</scope>

+    </dependency>

+  </dependencies>

+

+  <build>

+		<plugins>

+

+			<plugin>

+				<groupId>org.apache.maven.plugins</groupId>

+				<artifactId>maven-compiler-plugin</artifactId>

+				<configuration>

+					<source>1.4</source>

+					<target>1.4</target>

+				</configuration>

+			</plugin>

+

+			<plugin>

+			  <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-jar-plugin</artifactId>

+        <configuration>

+          <archive>

+            <manifestEntries>

+              <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>

+              <Bundle-Description>${project.description}</Bundle-Description>

+              <Implementation-Version>${project.version}</Implementation-Version>

+            </manifestEntries>

+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>

+          </archive>

+        </configuration>			

+      </plugin>

+    </plugins>

+  </build>

+

+</project>
\ No newline at end of file
diff --git a/lib/slf4j-jdk14-1.6.1.pom.sha1 b/lib/slf4j-jdk14-1.6.1.pom.sha1
new file mode 100644
index 0000000..169a426
--- /dev/null
+++ b/lib/slf4j-jdk14-1.6.1.pom.sha1
@@ -0,0 +1 @@
+6f0ca912337d5b0d1711f8e0f4f283cd13ec518d
\ No newline at end of file
diff --git a/pom.xml_saved b/pom.xml_saved
new file mode 100644
index 0000000..04141b4
--- /dev/null
+++ b/pom.xml_saved
@@ -0,0 +1,779 @@
+<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/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-parent</artifactId>
+    <version>20</version>
+  </parent>
+  <artifactId>jetty-project</artifactId>
+  <version>8.1.15.v20140411</version>
+  <name>Jetty :: Project</name>
+  <url>http://www.eclipse.org/jetty</url>
+  <packaging>pom</packaging>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <jetty.url>http://www.eclipse.org/jetty</jetty.url>
+    <orbit-servlet-api-version>3.0.0.v201112011016</orbit-servlet-api-version>
+    <build-support-version>1.1</build-support-version>
+    <slf4j-version>1.6.1</slf4j-version>
+    <jetty-test-policy-version>1.2</jetty-test-policy-version>
+  </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</url>
+  </scm>
+  <build>
+    <defaultGoal>install</defaultGoal>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <verbose>false</verbose>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-release-plugin</artifactId>
+        <configuration>
+          <autoVersionSubmodules>true</autoVersionSubmodules>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-remote-resources-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>generate-resources</phase>
+            <goals>
+              <goal>process</goal>
+            </goals>
+            <configuration>
+              <resourceBundles>
+                <resourceBundle>org.eclipse.jetty.toolchain:jetty-artifact-remote-resources:1.1</resourceBundle>
+              </resourceBundles>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- source maven plugin creates the source bundle and adds manifest -->
+      <plugin>
+        <inherited>true</inherited>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>process-classes</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <configuration>
+              <archive>
+                <manifestEntries>
+                  <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
+                  <Bundle-Name>${project.name}</Bundle-Name>
+                  <Bundle-SymbolicName>${bundle-symbolic-name}.source;singleton:=true</Bundle-SymbolicName>
+                  <Bundle-Vendor>Eclipse.org - Jetty</Bundle-Vendor>
+                  <Bundle-Version>${parsedVersion.osgiVersion}</Bundle-Version>
+                  <Eclipse-SourceBundle>${bundle-symbolic-name};version="${parsedVersion.osgiVersion}";roots:="."</Eclipse-SourceBundle>
+                </manifestEntries>
+              </archive>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- Build helper maven plugin sets the parsedVersion.osgiVersion property -->
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>set-osgi-version</id>
+            <phase>validate</phase>
+            <goals>
+              <goal>parse-version</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-version-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-version</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>attach-version-text</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- Enforcer Plugin -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>enforce-java</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <requireMavenVersion>
+                  <version>[2.0.6,)</version>
+                </requireMavenVersion>
+                <requireJavaVersion>
+                  <version>[1.5,)</version>
+                </requireJavaVersion>
+                <versionTxtRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.VersionTxtRule" />
+                <versionOsgiRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.RequireOsgiCompatibleVersionRule" />
+                <versionRedhatRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.RequireRedhatCompatibleVersionRule" />
+                <versionDebianRule implementation="org.eclipse.jetty.toolchain.enforcer.rules.RequireDebianCompatibleVersionRule" />
+             </rules>
+            </configuration>
+          </execution>
+          <execution>
+            <id>enforce-orbit-deps</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <!-- Banned Dependencies (should use Orbit based versions now) -->
+                <bannedDependencies>
+                  <includes>
+                    <include>javax.servlet</include>
+                    <include>org.apache.geronimo.specs</include>
+                    <include>javax.mail</include>
+                    <include>javax.activation</include>
+                  </includes>
+                  <searchTransitive>true</searchTransitive>
+                  <message>This dependency is banned, use the ORBIT provided dependency instead.</message>
+                </bannedDependencies>
+              </rules>
+              <fail>true</fail>
+            </configuration>
+          </execution>
+        </executions>
+        <dependencies>
+           <dependency>
+             <groupId>org.eclipse.jetty.toolchain</groupId>
+             <artifactId>jetty-build-support</artifactId>
+             <version>${build-support-version}</version>
+           </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>named-logging-enforcement</id>
+            <phase>compile</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+            <configuration>
+              <verbose>true</verbose>
+            </configuration>
+          </execution>
+        </executions>
+        <configuration>
+          <targetJdk>1.6</targetJdk>
+          <rulesets>
+            <ruleset>jetty/pmd_logging_ruleset.xml</ruleset>
+          </rulesets>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-build-support</artifactId>
+            <version>${build-support-version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <inherited>false</inherited>
+        <groupId>com.mycila.maven-license-plugin</groupId>
+        <artifactId>maven-license-plugin</artifactId>
+        <version>1.10.b1</version>
+        <configuration>
+          <header>header-template.txt</header>
+          <failIfMissing>true</failIfMissing>
+          <aggregate>true</aggregate>
+          <strictCheck>true</strictCheck>
+          <properties>
+             <copyright-range>${project.inceptionYear}-2014</copyright-range>
+          </properties>
+          <mapping>
+            <java>DOUBLESLASH_STYLE</java>
+          </mapping>
+          <includes>
+            <include>**/*.java</include>
+          </includes>
+          <excludes>
+            <exclude>jetty-util/src/main/java/org/eclipse/jetty/util/security/UnixCrypt.java</exclude>
+            <exclude>jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/DefaultPolicyLoader.java</exclude>
+            <exclude>jetty-policy/src/main/java/org/eclipse/jetty/policy/loader/PolicyFileScanner.java</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <id>check-headers</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+      <plugins>
+        <plugin>
+          <groupId>org.eclipse.jetty.toolchain</groupId>
+          <artifactId>jetty-version-maven-plugin</artifactId>
+          <version>1.0.10</version>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <configuration>
+            <archive>
+              <manifestEntries>
+                <Implementation-Version>${project.version}</Implementation-Version>
+                <Implementation-Vendor>Eclipse.org - Jetty</Implementation-Vendor>
+                <url>${jetty.url}</url>
+              </manifestEntries>
+            </archive>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+          <configuration>
+            <failIfNoTests>false</failIfNoTests>
+            <!--systemProperties>
+              <property>
+                <name>org.eclipse.jetty.io.AbstractBuffer.boundsChecking</name>
+                <value>true</value>
+              </property>
+            </systemProperties-->
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.felix</groupId>
+          <artifactId>maven-bundle-plugin</artifactId>
+          <extensions>true</extensions>
+          <configuration>
+            <instructions>
+              <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
+              <Bundle-DocURL>${jetty.url}</Bundle-DocURL>
+              <Bundle-Vendor>Eclipse Jetty Project</Bundle-Vendor>
+              <Bundle-Classpath>.</Bundle-Classpath>
+              <Export-Package>${bundle-symbolic-name}.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}"</Export-Package>
+              <Bundle-Copyright>Copyright (c) 2008-2014 Mort Bay Consulting Pty. Ltd.</Bundle-Copyright>
+              <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+            </instructions>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-assembly-plugin</artifactId>
+          <dependencies>
+            <dependency>
+              <groupId>org.eclipse.jetty.toolchain</groupId>
+              <artifactId>jetty-assembly-descriptors</artifactId>
+              <version>1.0</version>
+            </dependency>
+          </dependencies>
+        </plugin>
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>findbugs-maven-plugin</artifactId>
+          <configuration>
+            <findbugsXmlOutput>true</findbugsXmlOutput>
+            <xmlOutput>true</xmlOutput>
+            <effort>Max</effort>
+            <onlyAnalyze>org.eclipse.jetty.*</onlyAnalyze>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-dependency-plugin</artifactId>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jxr-plugin</artifactId>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-javadoc-plugin</artifactId>
+          <configuration>
+            <docfilessubdirs>true</docfilessubdirs>
+            <detectLinks>false</detectLinks>
+            <detectJavaApiLink>true</detectJavaApiLink>
+            <excludePackageNames>org.slf4j.*;org.mortbay.*</excludePackageNames>
+            <links>
+              <link>http://download.eclipse.org/jetty/stable-7/apidocs/</link>
+            </links>
+            <tags>
+              <tag>
+                <name>org.apache.xbean.XBean</name>
+                <placement>a</placement>
+                <head>Apache XBean:</head>
+              </tag>
+            </tags>
+            </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-pmd-plugin</artifactId>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.1</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.8</version>
+        <configuration>
+          <maxmemory>512m</maxmemory>
+          <docfilessubdirs>true</docfilessubdirs>
+          <detectLinks>true</detectLinks>
+          <detectJavaApiLink>true</detectJavaApiLink>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <version>2.7.1</version>
+        <configuration>
+          <targetJdk>1.5</targetJdk>
+          <rulesets>
+            <ruleset>jetty/pmd_ruleset.xml</ruleset>
+          </rulesets>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>2.3.2</version>
+      </plugin>
+    </plugins>
+  </reporting>
+<!--
+  <repositories>
+    <repository>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+      <id>sonatype-snapshots</id>
+      <name>Sonatype Jetty Snapshots</name>
+      <url>http://oss.sonatype.org/content/groups/jetty</url>
+    </repository>
+  </repositories>
+-->
+  <modules>
+    <module>jetty-util</module>
+    <module>jetty-jmx</module>
+    <module>jetty-io</module>
+    <module>jetty-http</module>
+    <module>jetty-websocket</module>
+    <module>jetty-continuation</module>
+    <module>jetty-server</module>
+    <module>jetty-client</module>
+    <module>jetty-xml</module>
+    <module>jetty-security</module>
+    <module>jetty-jaspi</module>
+    <module>jetty-servlet</module>
+    <module>jetty-webapp</module>
+    <module>jetty-servlets</module>
+    <module>jetty-deploy</module>
+    <module>jetty-ajp</module>
+    <module>jetty-jndi</module>
+    <module>jetty-annotations</module>
+    <module>jetty-plus</module>
+    <module>jetty-rewrite</module>
+    <module>jetty-policy</module>
+    <module>jetty-monitor</module>
+    <module>jetty-start</module>
+    <module>jetty-nested</module>
+    <module>jetty-overlay-deployer</module>
+    <module>jetty-nosql</module>
+    <module>jetty-http-spi</module>
+    <module>jetty-jsp</module>
+    <module>jetty-distribution</module>
+    <!--module>test-continuation-jetty6</module-->
+  </modules>
+  <dependencyManagement>
+    <dependencies>
+      <!-- Orbit Deps -->
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.servlet</artifactId>
+        <version>${orbit-servlet-api-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.annotation</artifactId>
+        <version>1.1.0.v201108011116</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>org.objectweb.asm</artifactId>
+        <version>3.1.0.v200803061910</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.activation</artifactId>
+        <version>1.1.0.v201105071233</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.mail.glassfish</artifactId>
+        <version>1.4.1.v201005082020</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.transaction</artifactId>
+        <version>1.1.1.v201105210645</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.security.auth.message</artifactId>
+        <version>1.0.0.v201108011116</version>
+      </dependency>
+      <!--
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.servlet.jsp</artifactId>
+        <version>2.1.0.v201105211820</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.servlet.jsp.jstl</artifactId>
+        <version>1.2.0.v201105211821</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>javax.el</artifactId>
+        <version>2.1.0.v201105211819</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>com.sun.el</artifactId>
+        <version>1.0.0.v201105211818</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>org.apache.jasper.glassfish</artifactId>
+        <version>2.1.0.v201110031002</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>org.apache.taglibs.standard.glassfish</artifactId>
+        <version>1.2.0.v201112081803</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.orbit</groupId>
+        <artifactId>org.eclipse.jdt.core</artifactId>
+        <version>3.7.1</version>
+      </dependency>
+        -->
+
+      <!-- Old Deps -->
+      <dependency>
+        <groupId>org.apache.maven</groupId>
+        <artifactId>maven-plugin-tools-api</artifactId>
+        <version>2.0</version>
+      </dependency>
+      <dependency>
+        <groupId>org.eclipse.jetty.toolchain</groupId>
+        <artifactId>jetty-test-helper</artifactId>
+        <version>2.5</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>jcl104-over-slf4j</artifactId>
+        <version>${slf4j-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>log4j-over-slf4j</artifactId>
+        <version>${slf4j-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-api</artifactId>
+        <version>${slf4j-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <version>4.11</version>
+      </dependency>
+      <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-core</artifactId>
+        <version>1.3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-library</artifactId>
+        <version>1.3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-core</artifactId>
+        <version>1.9.5</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <!--
+    Usage:
+    configure settings.xml for jetty.eclipse.website server entry
+    > mvn -Paggregate-site javadoc:aggregate jxr:jxr
+    > mvn -N site:deploy
+    or
+    > mvn -N site:sshdeploy     (for ssh users w/passphrase and ssh-agent)
+   -->
+  <profiles>
+    <profile>
+      <!-- Modules that are only for JDK7+ builds -->
+      <id>JDK7-plus-modules</id>
+      <activation>
+        <jdk>[1.7,)</jdk>
+      </activation>
+      <modules>
+        <module>jetty-spdy</module>
+      </modules>
+    </profile>
+    <profile>
+      <id>eclipse-release</id>
+   <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>enforce-java</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <requireJavaVersion>
+                  <version>[1.7,)</version>
+                </requireJavaVersion>
+              </rules>    
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+    </profile>
+    <profile>
+      <!--
+        Moves the jetty-aggregate build tree to a profile called 'aggregates'.
+        It is active by default, but being in a profile allows it to be
+        disabled via the "-P-aggregates" command line on maven.
+        (Useful for running site plugin with deep reporting and avoiding
+         duplicate hits on aggregated classes) -->
+      <id>aggregates</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+        <file>
+          <exists>${basedir}/pom.xml</exists>
+        </file>
+      </activation>
+      <modules>
+        <module>jetty-aggregate</module>
+      </modules>
+    </profile>
+    <profile>
+      <!--
+        Moves the jetty-osgi build tree to a profile called 'osgi'.
+        It is active by default, but being in a profile allows it to be
+        disabled via the "-P-osgi" command line on maven (if need be).
+        -->
+      <id>osgi</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+        <file>
+          <exists>${basedir}/pom.xml</exists>
+        </file>
+      </activation>
+      <modules>
+        <module>jetty-osgi</module>
+      </modules>
+    </profile>
+    <profile>
+      <id>update-version</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.eclipse.jetty.toolchain</groupId>
+            <artifactId>jetty-version-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>gen-versiontxt</id>
+                <phase>generate-resources</phase>
+                <goals>
+                  <goal>update-version-text</goal>
+                </goals>
+                <configuration>
+                  <refreshTags>true</refreshTags>
+                  <copyGenerated>true</copyGenerated>
+                  <attachArtifact>false</attachArtifact>
+                  <updateDate>true</updateDate>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>cobertura</id>
+      <reporting>
+        <plugins>
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>cobertura-maven-plugin</artifactId>
+            <configuration>
+              <maxmem>512m</maxmem>
+            </configuration>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <reportSets>
+              <reportSet>
+                <id>just-javadoc-no-aggregate</id>
+                <reports>
+                  <report>javadoc</report>
+                </reports>
+                <inherited>true</inherited>
+                <configuration>
+                  <minmemory>256m</minmemory>
+                  <maxmemory>1g</maxmemory>
+                  <excludePackageNames>com.acme</excludePackageNames>
+                  <links>
+                    <link>http://java.sun.com/j2se/1.5.0/docs/api</link>
+                    <link>http://java.sun.com/javaee/5/docs/api</link>
+                    <link>http://junit.sourceforge.net/javadoc/</link>
+                  </links>
+                  <tags>
+                    <tag>
+                      <name>org.apache.xbean.XBean</name>
+                      <placement>X</placement>
+                      <head />
+                    </tag>
+                  </tags>
+                </configuration>
+              </reportSet>
+            </reportSets>
+          </plugin>
+         </plugins>
+      </reporting>
+      <build>
+        <pluginManagement>
+          <plugins>
+            <plugin>
+              <groupId>org.apache.maven.plugins</groupId>
+              <artifactId>maven-surefire-plugin</artifactId>
+              <configuration>
+                <testFailureIgnore>true</testFailureIgnore>
+              </configuration>
+            </plugin>
+          </plugins>
+        </pluginManagement>
+      </build>
+    </profile>
+    <profile>
+      <id>maven-3</id>
+      <activation>
+        <file>
+          <!--  This employs that the basedir expression is only recognized by Maven 3.x (see MNG-2363) -->
+          <exists>${basedir}</exists>
+        </file>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <artifactId>maven-site-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>attach-descriptor</id>
+                <goals>
+                  <goal>attach-descriptor</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+    <profile>
+      <id>aggregate-site</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-jxr-plugin</artifactId>
+            <configuration>
+              <aggregate>true</aggregate>
+            </configuration>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <configuration>
+              <excludePackageNames>com.acme</excludePackageNames>
+              <links>
+                <link>http://java.sun.com/javase/6/docs/api/</link>
+                <link>http://java.sun.com/javaee/6/docs/api</link>
+                <link>http://junit.sourceforge.net/javadoc/</link>
+              </links>
+              <tags>
+                <tag>
+                  <name>org.apache.xbean.XBean</name>
+                  <placement>X</placement>
+                  <head />
+                </tag>
+              </tags>
+              <header>
+                <![CDATA[
+                    <script type="text/javascript">
+                      var _gaq = _gaq || [];
+                      _gaq.push(['_setAccount', 'UA-1149868-7']);
+                      _gaq.push(['_trackPageview']);
+                      (function() {
+                        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+                        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+                        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+                      })();
+                   </script>
+                ]]>
+              </header>           
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/src/assembly/config.xml b/src/assembly/config.xml
new file mode 100644
index 0000000..b0592ae
--- /dev/null
+++ b/src/assembly/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly>
+  <id>config</id>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <fileSets>
+
+    <fileSet>
+      <directory>src/main/config</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>**</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+
+</assembly>
+
diff --git a/src/assembly/site-component.xml b/src/assembly/site-component.xml
new file mode 100644
index 0000000..7c48a5b
--- /dev/null
+++ b/src/assembly/site-component.xml
@@ -0,0 +1,15 @@
+<assembly>
+  <id>site-component</id>
+  <formats>
+    <format>jar</format>
+  </formats>
+  <fileSets>
+    <fileSet>
+      <directory>${basedir}</directory>
+      <outputDirectory>jetty-xml</outputDirectory>
+      <includes>
+        <include>src/main/resources/org/eclipse/**</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/config/etc/README.spnego b/src/config/etc/README.spnego
new file mode 100644
index 0000000..0b4cc5b
--- /dev/null
+++ b/src/config/etc/README.spnego
@@ -0,0 +1,62 @@
+This setup will enable you to authenticate a user via spnego into your 
+webapp.
+
+To run with spengo enabled the following command line options are required:
+
+-Djava.security.krb5.conf=/path/to/jetty/etc/krb5.ini
+-Djava.security.auth.login.config=/path/to/jetty/etc/spnego.conf 
+-Djavax.security.auth.useSubjectCredsOnly=false
+
+The easiest place to put these lines are in the start.ini file.
+
+For debugging the spengo authentication the following options are helpful:
+
+-Dorg.eclipse.jetty.LEVEL=debug
+-Dsun.security.spnego.debug=true
+
+
+Spengo Authentication is enabled in the webapp with the following setup.
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Secure Area</web-resource-name>
+      <url-pattern>/secure/me/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>MORTBAY.ORG</role-name>  <-- this is the domain that the user is a member of
+    </auth-constraint>
+  </security-constraint>
+
+  <login-config>
+    <auth-method>SPNEGO</auth-method>
+    <realm-name>Test Realm</realm-name>
+    (optionally to add custom error page)
+    <spnego-login-config>
+      <spengo-error-page>/loginError.html?param=foo</spnego-error-page>
+    </spnego-login-config>
+  </login-config>
+   
+A corresponding UserRealm needs to be created either programmatically if 
+embedded, via the jetty.xml or in a context file for the webapp.
+
+(in the jetty.xml)
+
+   <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.security.SpnegoLoginService">
+          <Set name="name">Test Realm</Set>
+          <Set name="config"><Property name="jetty.home" default="."/>/etc/spnego.properties</Set>
+        </New>
+      </Arg>
+    </Call>
+
+(context file)
+  <Get name="securityHandler">
+    <Set name="loginService">
+      <New class="org.eclipse.jetty.security.SpnegoLoginService">
+	    <Set name="name">Test Realm</Set>
+	    <Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/spnego.properties</Set>
+      </New>
+    </Set>
+    <Set name="checkWelcomeFiles">true</Set>
+  </Get>
\ No newline at end of file
diff --git a/src/config/etc/jdbcRealm.properties b/src/config/etc/jdbcRealm.properties
new file mode 100644
index 0000000..48104d8
--- /dev/null
+++ b/src/config/etc/jdbcRealm.properties
@@ -0,0 +1,72 @@
+# 
+# This is a sample properties file for the org.eclipse.jetty.security.JDBCLoginService
+# implemtation of the UserRealm interface.  This allows Jetty users authentication 
+# to work from a database.
+#
+#   +-------+      +------------+      +-------+
+#   | users |      | user_roles |      | roles |
+#   +-------+      +------------+      +-------+
+#   | id    |     /| user_id    |\     | id    |
+#   | user  -------| role_id    |------- role  |
+#   | pwd   |     \|            |/     |       |
+#   +-------+      +------------+      +-------+
+#   
+# 
+# 'cachetime' is a time in seconds to cache positive database
+# lookups in internal hash table. Set to 0 to disable caching.
+# 
+#
+# For MySQL:
+# create a MYSQL user called "jetty" with password "jetty"
+#
+# Create the tables:
+# create table users 
+# (
+#     id integer primary key,
+#     username varchar(100) not null unique key,
+#     pwd varchar(20) not null
+# );
+# 
+# create table roles
+# (
+#     id integer primary key,
+#     role varchar(100) not null unique key
+# );    
+#
+# create table user_roles
+# (
+#     user_id integer not null,
+#     role_id integer not null,
+#     unique key (user_id, role_id),
+#     index(user_id)
+# );
+#
+# I'm not sure unique key with a first component of user_id will be
+# user by MySQL in query, so additional index wouldn't hurt.
+#
+# To test JDBC implementation:
+#
+# mysql> insert into users values (1, 'admin', 'password');
+# mysql> insert into roles values (1, 'server-administrator');
+# mysql> insert into roles values (2, 'content-administrator');
+# mysql> insert into user_roles values (1, 1);
+# mysql> insert into user_roles values (1, 2);
+#
+# Replace HashUserRealm in etc/admin.xml with JDBCUserRealm and
+# set path to properties file.
+#
+jdbcdriver = org.gjt.mm.mysql.Driver
+url = jdbc:mysql://localhost/jetty
+username = jetty
+password = jetty
+usertable = users
+usertablekey = id
+usertableuserfield = username
+usertablepasswordfield = pwd
+roletable = roles
+roletablekey = id
+roletablerolefield = role
+userroletable = user_roles
+userroletableuserkey = user_id
+userroletablerolekey = role_id
+cachetime = 300
diff --git a/src/config/etc/jetty-bio-ssl.xml b/src/config/etc/jetty-bio-ssl.xml
new file mode 100644
index 0000000..3386b7b
--- /dev/null
+++ b/src/config/etc/jetty-bio-ssl.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure SSL for the Jetty Server                              -->
+<!-- this configuration file should be used in combination with      -->
+<!-- other configuration files.  e.g.                                -->
+<!--    java -jar start.jar etc/jetty-ssl.xml                        -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ssl.SslSocketConnector">
+	<Set name="Port">9443</Set>
+	<Set name="maxIdleTime">30000</Set>
+	<Set name="Keystore"><Property name="jetty.home" default="." />/etc/keystore</Set>
+	<Set name="Password">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+	<Set name="KeyPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
+        <Set name="truststore"><Property name="jetty.home" default="." />/etc/keystore</Set>
+        <Set name="trustPassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/src/config/etc/jetty-bio.xml b/src/config/etc/jetty-bio.xml
new file mode 100644
index 0000000..66950ee
--- /dev/null
+++ b/src/config/etc/jetty-bio.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin configuration for Block socket connector                  -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- Use this connector if NIO is not available.  -->
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.bio.SocketConnector">
+            <Set name="port"><Property name="jetty.bio.port" default="8081"/></Set>
+            <Set name="maxIdleTime">50000</Set>
+            <Set name="lowResourceMaxIdleTime">1500</Set>
+          </New>
+      </Arg>
+    </Call>
+
+</Configure>
diff --git a/src/config/etc/jetty-debug.xml b/src/config/etc/jetty-debug.xml
new file mode 100644
index 0000000..6d66953
--- /dev/null
+++ b/src/config/etc/jetty-debug.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the DebugHandler                                          -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Get id="oldhandler" name="handler"/>
+    <Set name="handler">
+      <New id="DebugHandler" class="org.eclipse.jetty.server.handler.DebugHandler">
+        <Set name="handler"><Ref id="oldhandler"/></Set>
+	<Set name="outputStream">
+	  <New class="org.eclipse.jetty.util.RolloverFileOutputStream">
+	    <Arg type="String"><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.debug.log</Arg>
+	    <Arg type="boolean">true</Arg> <!-- append -->
+	    <Arg type="int">90</Arg> <!-- retain days -->
+	  </New>
+	</Set>
+      </New>
+    </Set>
+</Configure>
diff --git a/src/config/etc/jetty-fileserver.xml b/src/config/etc/jetty-fileserver.xml
new file mode 100644
index 0000000..de15b38
--- /dev/null
+++ b/src/config/etc/jetty-fileserver.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+
+<Configure id="FileServer" class="org.eclipse.jetty.server.Server">
+
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+            <Set name="port">8080</Set>
+          </New>
+      </Arg>
+    </Call>
+
+    <Set name="handler">
+      <New class="org.eclipse.jetty.server.handler.HandlerList">
+        <Set name="handlers">
+	  <Array type="org.eclipse.jetty.server.Handler">
+	    <Item>
+	      <New class="org.eclipse.jetty.server.handler.ResourceHandler">
+	        <Set name="directoriesListed">true</Set>
+		<Set name="welcomeFiles">
+		  <Array type="String"><Item>index.html</Item></Array>
+		</Set>
+	        <Set name="resourceBase">.</Set>
+	      </New>
+	    </Item>
+	    <Item>
+	      <New class="org.eclipse.jetty.server.handler.DefaultHandler">
+	      </New>
+	    </Item>
+	  </Array>
+        </Set>
+      </New>
+    </Set>
+    
+</Configure>
diff --git a/src/config/etc/jetty-ipaccess.xml b/src/config/etc/jetty-ipaccess.xml
new file mode 100644
index 0000000..d5fb5f8
--- /dev/null
+++ b/src/config/etc/jetty-ipaccess.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Statistics Handler                                    -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <Get id="oldhandler" name="handler"/>
+
+    <Set name="handler">
+     <New id="IPAccessHandler" class="org.eclipse.jetty.server.handler.IPAccessHandler">
+      <Set name="handler"><Ref id="oldhandler"/></Set>
+      <Set name="white">
+        <Array type="String">
+	  <Item>127.0.0.1</Item>
+	  <Item>127.0.0.2/*.html</Item>
+	</Array>
+      </Set>
+      <Set name="black">
+        <Array type="String">
+	  <Item>127.0.0.1/blacklisted</Item>
+	  <Item>127.0.0.2/black.html</Item>
+	</Array>
+      </Set>
+     </New>
+    </Set>
+    
+</Configure>
diff --git a/src/config/etc/jetty-jmx.xml b/src/config/etc/jetty-jmx.xml
new file mode 100644
index 0000000..4db0dbb
--- /dev/null
+++ b/src/config/etc/jetty-jmx.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- ============================================================================ -->
+<!-- To correctly start Jetty with JMX module enabled, this configuration         -->
+<!-- file must appear first in the list of the configuration files.               -->
+<!-- The simplest way to achieve this is to add etc/jetty-jmx.xml as the          -->
+<!-- first file in configuration file list at the end of start.ini file.          -->
+<!-- ============================================================================ -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- =========================================================== -->
+  <!-- Set the java.rmi.server.hostname property in case you've    -->
+  <!-- got a misconfigured /etc/hosts entry or the like.           -->
+  <!-- =========================================================== -->
+  <!-- 
+  <Call class="java.lang.System" name="setProperty">
+    <Arg>java.rmi.server.hostname</Arg>
+    <Arg>127.0.0.1</Arg>
+  </Call>
+  -->
+  
+  <!-- =========================================================== -->
+  <!-- Initialize an mbean server                                  -->
+  <!-- =========================================================== -->
+  <Call id="MBeanServer" class="java.lang.management.ManagementFactory"
+    name="getPlatformMBeanServer" />
+
+  <!-- =========================================================== -->
+  <!-- Initialize the Jetty MBean container                        -->
+  <!-- =========================================================== -->
+  <New id="MBeanContainer" class="org.eclipse.jetty.jmx.MBeanContainer">
+    <Arg><Ref id="MBeanServer" /></Arg>
+    <Call name="start"/>
+  </New>
+
+  <!-- Add to the Server to listen for object events -->
+  <Get id="Container" name="container">
+    <Call name="addEventListener">
+      <Arg><Ref id="MBeanContainer" /></Arg>
+    </Call>
+  </Get>
+
+  <!-- Add to the Server as a managed lifecycle -->
+  <Call name="addBean">
+    <Arg><Ref id="MBeanContainer"/></Arg>
+    <Arg type="boolean">true</Arg>
+  </Call>
+
+  <!-- Add the static log -->
+  <Ref id="MBeanContainer">
+    <Call name="addBean">
+      <Arg>
+        <New class="org.eclipse.jetty.util.log.Log"/>
+      </Arg>
+    </Call>
+  </Ref>
+  
+  <!-- In order to connect to the JMX server remotely from a different
+       process, possibly running on a different host, Jetty JMX module
+       can create a remote JMX connector. It requires RMI registry to
+       be started prior to creating the connector server because the
+       JMX specification uses RMI to facilitate connections.        
+   -->
+
+  <!-- Optionally start the RMI registry. Normally RMI registry runs on
+       port 1099. The argument below can be changed in order to comply
+       with the firewall requirements.
+  -->
+  <!--
+  <Call name="createRegistry" class="java.rmi.registry.LocateRegistry">
+    <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
+    <Call name="sleep" class="java.lang.Thread">
+       <Arg type="java.lang.Integer">1000</Arg>
+    </Call>
+  </Call>
+  -->
+ 
+  <!-- Optionally add a remote JMX connector. The parameters of the constructor
+       below specify the JMX service URL, and the object name string for the
+       connector server bean. The parameters of the JMXServiceURL constructor 
+       specify the protocol that clients will use to connect to the remote JMX
+       connector (RMI), the hostname of the server (local hostname), port number
+       (automatically assigned), and the URL path. Note that URL path contains
+       the RMI registry hostname and port number, that may need to be modified
+       in order to comply with the firewall requirements. 
+  -->
+  <!--
+  <New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
+    <Arg>
+      <New class="javax.management.remote.JMXServiceURL">
+        <Arg type="java.lang.String">rmi</Arg>
+        <Arg type="java.lang.String" />
+        <Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
+        <Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
+      </New>
+    </Arg>
+    <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
+    <Call name="start" />
+  </New>
+  -->
+</Configure>
+
diff --git a/src/config/etc/jetty-logging.xml b/src/config/etc/jetty-logging.xml
new file mode 100644
index 0000000..2060a22
--- /dev/null
+++ b/src/config/etc/jetty-logging.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+
+<!-- =============================================================== -->
+<!-- Configure stderr and stdout to a Jetty rollover log file        -->
+<!-- this configuration file should be used in combination with      -->
+<!-- other configuration files.  e.g.                                -->
+<!--    java -jar start.jar etc/jetty-logging.xml                    -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <New id="ServerLog" class="java.io.PrintStream">
+      <Arg>
+        <New class="org.eclipse.jetty.util.RolloverFileOutputStream">
+          <Arg><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.stderrout.log</Arg>
+          <Arg type="boolean">false</Arg>
+          <Arg type="int">90</Arg>
+          <Arg><Call class="java.util.TimeZone" name="getTimeZone"><Arg>GMT</Arg></Call></Arg>
+          <Get id="ServerLogName" name="datedFilename"/>
+        </New>
+      </Arg>
+    </New>
+
+    <Call class="org.eclipse.jetty.util.log.Log" name="info"><Arg>Redirecting stderr/stdout to <Ref id="ServerLogName"/></Arg></Call>
+    <Call class="java.lang.System" name="setErr"><Arg><Ref id="ServerLog"/></Arg></Call>
+    <Call class="java.lang.System" name="setOut"><Arg><Ref id="ServerLog"/></Arg></Call>
+
+</Configure>
+
+
+
diff --git a/src/config/etc/jetty-proxy.xml b/src/config/etc/jetty-proxy.xml
new file mode 100644
index 0000000..5bc46c3
--- /dev/null
+++ b/src/config/etc/jetty-proxy.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server                                      -->
+<!--                                                                 -->
+<!-- Documentation of this file format can be found at:              -->
+<!-- http://docs.codehaus.org/display/JETTY/jetty.xml                -->
+<!--                                                                 -->
+<!-- =============================================================== -->
+
+
+<Configure id="Proxy" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+      <!-- Default queued blocking threadpool 
+      -->
+      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+        <Set name="minThreads">10</Set>
+        <Set name="maxThreads">50</Set>
+      </New>
+    </Set>
+
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+            <Set name="host"><Property name="jetty.host" /></Set>
+            <Set name="port"><Property name="jetty.port" default="8888"/></Set>
+            <Set name="maxIdleTime">300000</Set>
+            <Set name="Acceptors">2</Set>
+            <Set name="statsOn">false</Set>
+	    <Set name="lowResourcesConnections">20000</Set>
+	    <Set name="lowResourcesMaxIdleTime">5000</Set>
+          </New>
+      </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <Set name="handler">
+      <New id="Servlets" class="org.eclipse.jetty.servlet.ServletHandler">
+        <Call name="addServletWithMapping">
+	  <Arg>org.eclipse.jetty.servlets.ProxyServlet</Arg>
+	  <Arg>/</Arg>
+	</Call>
+      </New>
+    </Set>
+    
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <Set name="sendServerVersion">true</Set>
+    <Set name="sendDateHeader">true</Set>
+    <Set name="gracefulShutdown">1000</Set>
+
+</Configure>
diff --git a/src/config/etc/jetty-requestlog.xml b/src/config/etc/jetty-requestlog.xml
new file mode 100644
index 0000000..45d3aab
--- /dev/null
+++ b/src/config/etc/jetty-requestlog.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Request Log                                 -->
+<!-- =============================================================== -->
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Configure Request Log                                       -->
+    <!-- =========================================================== -->
+    <Ref id="Handlers">
+      <Call name="addHandler">
+        <Arg>
+          <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler">
+	    <Set name="requestLog">
+	      <!-- Use AsyncNCSARequestLog for improved request latency -->
+	      <New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
+		<Set name="filename"><Property name="jetty.logs" default="./logs"/>/yyyy_mm_dd.request.log</Set>
+		<Set name="filenameDateFormat">yyyy_MM_dd</Set>
+		<Set name="retainDays">90</Set>
+		<Set name="append">true</Set>
+		<Set name="extended">false</Set>
+		<Set name="logCookies">false</Set>
+		<Set name="LogTimeZone">GMT</Set>
+	      </New>
+	    </Set>
+	  </New>
+        </Arg>
+      </Call>
+    </Ref>
+
+</Configure>
diff --git a/src/config/etc/jetty-ssl.xml b/src/config/etc/jetty-ssl.xml
new file mode 100644
index 0000000..4c0c9ff
--- /dev/null
+++ b/src/config/etc/jetty-ssl.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure SSL for the Jetty Server                              -->
+<!-- this configuration file should be used in combination with      -->
+<!-- other configuration files.  e.g.                                -->
+<!--    java -jar start.jar etc/jetty-ssl.xml                        -->
+<!--                                                                 -->
+<!--  alternately, add to the start.ini for easier usage             -->
+<!-- =============================================================== -->
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+  <!-- if NIO is not available, use org.eclipse.jetty.server.ssl.SslSocketConnector -->
+  
+  <New id="sslContextFactory" class="org.eclipse.jetty.http.ssl.SslContextFactory">
+    <Set name="KeyStore"><Property name="jetty.home" default="." />/etc/keystore</Set>
+    <Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+    <Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
+    <Set name="TrustStore"><Property name="jetty.home" default="." />/etc/keystore</Set>
+    <Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
+  </New>
+
+  <Call name="addConnector">
+    <Arg>
+      <New class="org.eclipse.jetty.server.ssl.SslSelectChannelConnector">
+        <Arg><Ref id="sslContextFactory" /></Arg>
+        <Set name="Port">8443</Set>
+        <Set name="maxIdleTime">30000</Set>
+        <Set name="Acceptors">2</Set>
+        <Set name="AcceptQueueSize">100</Set>
+      </New>
+    </Arg>
+  </Call>
+</Configure>
diff --git a/src/config/etc/jetty-stats.xml b/src/config/etc/jetty-stats.xml
new file mode 100644
index 0000000..e0a0c7f
--- /dev/null
+++ b/src/config/etc/jetty-stats.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Mixin the Statistics Handler                                    -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <Get id="oldhandler" name="handler"/>
+
+    <Set name="handler">
+     <New id="StatsHandler" class="org.eclipse.jetty.server.handler.StatisticsHandler">
+      <Set name="handler"><Ref id="oldhandler"/></Set>
+     </New>
+    </Set>
+    
+</Configure>
diff --git a/src/config/etc/jetty-xinetd.xml b/src/config/etc/jetty-xinetd.xml
new file mode 100644
index 0000000..c2fbaa2
--- /dev/null
+++ b/src/config/etc/jetty-xinetd.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configuration for starting up Jetty using inetd/xinetd          -->
+<!-- This feature requires at least Java 5                           -->
+<!--                                                                 -->
+<!-- Making it a mixin for convenience, but note that if used        -->
+<!-- with jetty.xml, Jetty will use multiple connectors              -->
+<!-- =============================================================== -->
+
+<!-- Sample xinetd configuration (restart xinetd after adding the configuration file)
+
+service jetty
+{
+    disable     = no
+
+    id          = jetty
+    type        = UNLISTED     
+    wait        = yes          
+    socket_type = stream
+
+    # change this
+    user        = username
+    group       = groupname
+    port        = 2001
+
+    # sample script for running jetty as a service
+    # replace $JETTY_HOME with /path/to/jetty_home/
+    server      = $JETTY_HOME/bin/jetty-xinetd.sh
+}
+
+-->
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+    <Call name="addConnector">
+      <Arg>
+          <!-- Inherited channel (from inetd/xinetd) -->
+          <New class="org.eclipse.jetty.server.nio.InheritedChannelConnector">
+
+
+            <!-- Optional. Fallback in case System.inheritedChannel() does not give a ServerSocketChannel 
+            <Set name="port"><Property name="jetty.service.port" default="8082"/></Set>
+            -->
+
+            <!-- sane defaults -->
+            <Set name="maxIdleTime">300000</Set>
+            <Set name="Acceptors">2</Set>
+            <Set name="statsOn">false</Set>
+      	    <Set name="lowResourcesConnections">20000</Set>
+	        <Set name="lowResourcesMaxIdleTime">5000</Set>
+          </New>
+      </Arg>
+    </Call>
+</Configure>
+
diff --git a/src/config/etc/jetty.xml b/src/config/etc/jetty.xml
new file mode 100644
index 0000000..153514d
--- /dev/null
+++ b/src/config/etc/jetty.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+
+<!-- =============================================================== -->
+<!-- Configure the Jetty Server                                      -->
+<!--                                                                 -->
+<!-- Documentation of this file format can be found at:              -->
+<!-- http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax        -->
+<!--                                                                 -->
+<!-- Additional configuration files are available in $JETTY_HOME/etc -->
+<!-- and can be mixed in.  For example:                              -->
+<!--   java -jar start.jar etc/jetty-ssl.xml                         -->
+<!--                                                                 -->
+<!-- See start.ini file for the default configuraton files           -->
+<!-- =============================================================== -->
+
+
+<Configure id="Server" class="org.eclipse.jetty.server.Server">
+
+    <!-- =========================================================== -->
+    <!-- Server Thread Pool                                          -->
+    <!-- =========================================================== -->
+    <Set name="ThreadPool">
+      <!-- Default queued blocking threadpool -->
+      <New class="org.eclipse.jetty.util.thread.QueuedThreadPool">
+        <Set name="minThreads">10</Set>
+        <Set name="maxThreads">200</Set>
+        <Set name="detailedDump">false</Set>
+      </New>
+    </Set>
+
+    <!-- =========================================================== -->
+    <!-- Set connectors                                              -->
+    <!-- =========================================================== -->
+
+    <Call name="addConnector">
+      <Arg>
+          <New class="org.eclipse.jetty.server.nio.SelectChannelConnector">
+            <Set name="host"><Property name="jetty.host" /></Set>
+            <Set name="port"><Property name="jetty.port" default="8080"/></Set>
+            <Set name="maxIdleTime">300000</Set>
+            <Set name="Acceptors">2</Set>
+            <Set name="statsOn">false</Set>
+            <Set name="confidentialPort">8443</Set>
+	    <Set name="lowResourcesConnections">20000</Set>
+	    <Set name="lowResourcesMaxIdleTime">5000</Set>
+          </New>
+      </Arg>
+    </Call>
+
+    <!-- =========================================================== -->
+    <!-- Set handler Collection Structure                            --> 
+    <!-- =========================================================== -->
+    <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="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
+           </Item>
+           <Item>
+             <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+           </Item>
+         </Array>
+        </Set>
+      </New>
+    </Set>
+
+    <!-- =========================================================== -->
+    <!-- extra options                                               -->
+    <!-- =========================================================== -->
+    <Set name="stopAtShutdown">true</Set>
+    <Set name="sendServerVersion">true</Set>
+    <Set name="sendDateHeader">true</Set>
+    <Set name="gracefulShutdown">1000</Set>
+    <Set name="dumpAfterStart">false</Set>
+    <Set name="dumpBeforeStop">false</Set>
+
+</Configure>
diff --git a/src/config/etc/keystore b/src/config/etc/keystore
new file mode 100644
index 0000000..08f6cda
--- /dev/null
+++ b/src/config/etc/keystore
Binary files differ
diff --git a/src/config/etc/krb5.ini b/src/config/etc/krb5.ini
new file mode 100644
index 0000000..9cea63c
--- /dev/null
+++ b/src/config/etc/krb5.ini
@@ -0,0 +1,23 @@
+[libdefaults]
+default_realm = MORTBAY.ORG
+default_keytab_name = FILE:/path/to/jetty/etc/krb5.keytab
+permitted_enctypes = aes128-cts aes256-cts arcfour-hmac-md5 
+default_tgs_enctypes = aes128-cts aes256-cts arcfour-hmac-md5 
+default_tkt_enctypes = aes128-cts aes256-cts arcfour-hmac-md5 
+
+
+
+[realms]
+MORTBAY.ORG = {
+ 		kdc = 192.168.2.30
+ 		admin_server = 192.168.2.30
+ 		default_domain = MORTBAY.ORG
+}
+
+[domain_realm]
+mortbay.org= MORTBAY.ORG
+.mortbay.org = MORTBAY.ORG
+
+[appdefaults]
+autologin = true
+forwardable = true
diff --git a/src/config/etc/spnego.conf b/src/config/etc/spnego.conf
new file mode 100644
index 0000000..3d5caf8
--- /dev/null
+++ b/src/config/etc/spnego.conf
@@ -0,0 +1,19 @@
+com.sun.security.jgss.initiate {
+     com.sun.security.auth.module.Krb5LoginModule required
+     principal="HTTP/vm.mortbay.org@MORTBAY.ORG" 
+     keyTab="/path/to/jetty/etc/krb5.keytab" 
+     useKeyTab=true
+     storeKey=true 
+     debug=true 
+     isInitiator=false;
+};
+ 
+com.sun.security.jgss.accept {
+     com.sun.security.auth.module.Krb5LoginModule required
+     principal="HTTP/vm.mortbay.org@MORTBAY.ORG" 
+     useKeyTab=true
+     keyTab="/path/to/jetty/etc/krb5.keytab" 
+     storeKey=true 
+     debug=true 
+     isInitiator=false;
+};
diff --git a/src/config/etc/spnego.properties b/src/config/etc/spnego.properties
new file mode 100644
index 0000000..86862ea
--- /dev/null
+++ b/src/config/etc/spnego.properties
@@ -0,0 +1 @@
+targetName = HTTP/vm.mortbay.org
\ No newline at end of file
diff --git a/src/config/etc/webdefault.xml b/src/config/etc/webdefault.xml
new file mode 100644
index 0000000..213138b
--- /dev/null
+++ b/src/config/etc/webdefault.xml
@@ -0,0 +1,527 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+  <!-- ===================================================================== -->
+  <!-- This file contains the default descriptor for web applications.       -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
+  <!-- The intent of this descriptor is to include jetty specific or common  -->
+  <!-- configuration for all webapps.   If a context has a webdefault.xml    -->
+  <!-- descriptor, it is applied before the contexts own web.xml file        -->
+  <!--                                                                       -->
+  <!-- A context may be assigned a default descriptor by:                    -->
+  <!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
+  <!--  + Passed an arg to addWebApplications                                -->
+  <!--                                                                       -->
+  <!-- This file is used both as the resource within the jetty.jar (which is -->
+  <!-- used as the default if no explicit defaults descriptor is set) and it -->
+  <!-- is copied to the etc directory of the Jetty distro and explicitly     -->
+  <!-- by the jetty.xml file.                                                -->
+  <!--                                                                       -->
+  <!-- ===================================================================== -->
+<web-app
+  xmlns="http://java.sun.com/xml/ns/javaee"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  metadata-complete="true"
+  version="2.5"
+>
+
+  <description>
+    Default web.xml file.  
+    This file is applied to a Web application before it's own WEB_INF/web.xml file
+  </description>
+
+  <!-- ==================================================================== -->
+  <!-- Removes static references to beans from javax.el.BeanELResolver to   -->
+  <!-- ensure webapp classloader can be released on undeploy                -->
+  <!-- ==================================================================== -->
+  <listener>
+   <listener-class>org.eclipse.jetty.servlet.listener.ELContextCleaner</listener-class>
+  </listener>
+  
+  <!-- ==================================================================== -->
+  <!-- Removes static cache of Methods from java.beans.Introspector to      -->
+  <!-- ensure webapp classloader can be released on undeploy                -->
+  <!-- ==================================================================== -->  
+  <listener>
+   <listener-class>org.eclipse.jetty.servlet.listener.IntrospectorCleaner</listener-class>
+  </listener>
+  
+
+  <!-- ==================================================================== -->
+  <!-- Context params to control Session Cookies                            -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!--
+    UNCOMMENT TO ACTIVATE <context-param> <param-name>org.eclipse.jetty.servlet.SessionDomain</param-name> <param-value>127.0.0.1</param-value> </context-param> <context-param>
+    <param-name>org.eclipse.jetty.servlet.SessionPath</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>org.eclipse.jetty.servlet.MaxAge</param-name>
+    <param-value>-1</param-value> </context-param>
+  -->
+
+  <!-- ==================================================================== -->
+  <!-- The default servlet.                                                 -->
+  <!-- This servlet, normally mapped to /, provides the handling for static -->
+  <!-- content, OPTIONS and TRACE methods for the context.                  -->
+  <!-- The following initParameters are supported:                          -->
+  <!--  
+ *  acceptRanges      If true, range requests and responses are
+ *                    supported
+ *
+ *  dirAllowed        If true, directory listings are returned if no
+ *                    welcome file is found. Else 403 Forbidden.
+ *
+ *  welcomeServlets   If true, attempt to dispatch to welcome files
+ *                    that are servlets, but only after no matching static
+ *                    resources could be found. If false, then a welcome
+ *                    file must exist on disk. If "exact", then exact
+ *                    servlet matches are supported without an existing file.
+ *                    Default is true.
+ *
+ *                    This must be false if you want directory listings,
+ *                    but have index.jsp in your welcome file list.
+ *
+ *  redirectWelcome   If true, welcome files are redirected rather than
+ *                    forwarded to.
+ *
+ *  gzip              If set to true, then static content will be served as
+ *                    gzip content encoded if a matching resource is
+ *                    found ending with ".gz"
+ *
+ *  resourceBase      Set to replace the context resource base
+ *
+ *  resourceCache     If set, this is a context attribute name, which the servlet 
+ *                    will use to look for a shared ResourceCache instance. 
+ *                        
+ *  relativeResourceBase
+ *                    Set with a pathname relative to the base of the
+ *                    servlet context root. Useful for only serving static content out
+ *                    of only specific subdirectories.
+ *
+ *  aliases           If True, aliases of resources are allowed (eg. symbolic
+ *                    links and caps variations). May bypass security constraints.
+ *
+ *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+ *  maxCachedFileSize The maximum size of a file to cache
+ *  maxCachedFiles    The maximum number of files to cache
+ *
+ *  useFileMappedBuffer
+ *                    If set to true, it will use mapped file buffer to serve static content
+ *                    when using NIO connector. Setting this value to false means that
+ *                    a direct buffer will be used instead of a mapped file buffer.
+ *                    By default, this is set to true.
+ *
+ *  cacheControl      If set, all static content will have this value set as the cache-control
+ *                    header.
+ -->
+ 
+ 
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet>
+    <servlet-name>default</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+    <init-param>
+      <param-name>aliases</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>acceptRanges</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>dirAllowed</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>welcomeServlets</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>redirectWelcome</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCacheSize</param-name>
+      <param-value>256000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFileSize</param-name>
+      <param-value>200000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>maxCachedFiles</param-name>
+      <param-value>2048</param-value>
+    </init-param>
+    <init-param>
+      <param-name>gzip</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <init-param>
+      <param-name>useFileMappedBuffer</param-name>
+      <param-value>true</param-value>
+    </init-param>
+    <!--
+    <init-param>
+      <param-name>resourceCache</param-name>
+      <param-value>resourceCache</param-value>
+    </init-param>
+    -->
+    <!--
+    <init-param>
+      <param-name>cacheControl</param-name>
+      <param-value>max-age=3600,public</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>default</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+
+
+  <!-- ==================================================================== -->
+  <!-- JSP Servlet                                                          -->
+  <!-- This is the jasper JSP servlet from the jakarta project              -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
+  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
+  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
+  <!-- following initialization parameters (default values are in square    -->
+  <!-- brackets):                                                           -->
+  <!--                                                                      -->
+  <!--   checkInterval       If development is false and reloading is true, -->
+  <!--                       background compiles are enabled. checkInterval -->
+  <!--                       is the time in seconds between checks to see   -->
+  <!--                       if a JSP page needs to be recompiled. [300]    -->
+  <!--                                                                      -->
+  <!--   compiler            Which compiler Ant should use to compile JSP   -->
+  <!--                       pages.  See the Ant documenation for more      -->
+  <!--                       information. [javac]                           -->
+  <!--                                                                      -->
+  <!--   classdebuginfo      Should the class file be compiled with         -->
+  <!--                       debugging information?  [true]                 -->
+  <!--                                                                      -->
+  <!--   classpath           What class path should I use while compiling   -->
+  <!--                       generated servlets?  [Created dynamically      -->
+  <!--                       based on the current web application]          -->
+  <!--                       Set to ? to make the container explicitly set  -->
+  <!--                       this parameter.                                -->
+  <!--                                                                      -->
+  <!--   development         Is Jasper used in development mode (will check -->
+  <!--                       for JSP modification on every access)?  [true] -->
+  <!--                                                                      -->
+  <!--   enablePooling       Determines whether tag handler pooling is      -->
+  <!--                       enabled  [true]                                -->
+  <!--                                                                      -->
+  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
+  <!--                       a separate JVM is used for JSP page compiles   -->
+  <!--                       from the one Tomcat is running in. [true]      -->
+  <!--                                                                      -->
+  <!--   ieClassId           The class-id value to be sent to Internet      -->
+  <!--                       Explorer when using <jsp:plugin> tags.         -->
+  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
+  <!--                                                                      -->
+  <!--   javaEncoding        Java file encoding to use for generating java  -->
+  <!--                       source files. [UTF-8]                          -->
+  <!--                                                                      -->
+  <!--   keepgenerated       Should we keep the generated Java source code  -->
+  <!--                       for each page instead of deleting it? [true]   -->
+  <!--                                                                      -->
+  <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
+  <!--                       by this servlet.  Increasing levels cause the  -->
+  <!--                       generation of more messages.  Valid values are -->
+  <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
+  <!--                       [WARNING]                                      -->
+  <!--                                                                      -->
+  <!--   mappedfile          Should we generate static content with one     -->
+  <!--                       print statement per input line, to ease        -->
+  <!--                       debugging?  [false]                            -->
+  <!--                                                                      -->
+  <!--                                                                      -->
+  <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
+  <!--                                                                      -->
+  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
+  <!--                       debugging be suppressed?  [false]              -->
+  <!--                                                                      -->
+  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
+  <!--                       dumped to a file? [false]                      -->
+  <!--                       False if suppressSmap is true                  -->
+  <!--                                                                      -->
+  <!--   scratchdir          What scratch directory should we use when      -->
+  <!--                       compiling JSP pages?  [default work directory  -->
+  <!--                       for the current web application]               -->
+  <!--                                                                      -->
+  <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
+  <!--                                                                      -->
+  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
+  <!--                       header is added by generated servlet  [false]  -->
+  <!--                                                                      -->
+  <!-- If you wish to use Jikes to compile JSP pages:                       -->
+  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
+  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
+  <!--   to cause Jikes to emit error messages in a format compatible with  -->
+  <!--   Jasper.                                                            -->
+  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
+  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <servlet
+    id="jsp"
+  >
+    <servlet-name>jsp</servlet-name>
+    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
+    <init-param>
+      <param-name>logVerbosityLevel</param-name>
+      <param-value>DEBUG</param-value>
+    </init-param>
+    <init-param>
+      <param-name>fork</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <init-param>
+      <param-name>xpoweredBy</param-name>
+      <param-value>false</param-value>
+    </init-param>
+    <!--  
+    <init-param>
+        <param-name>classpath</param-name>
+        <param-value>?</param-value>
+    </init-param>
+    -->
+    <load-on-startup>0</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>jsp</servlet-name>
+    <url-pattern>*.jsp</url-pattern>
+    <url-pattern>*.jspf</url-pattern>
+    <url-pattern>*.jspx</url-pattern>
+    <url-pattern>*.xsp</url-pattern>
+    <url-pattern>*.JSP</url-pattern>
+    <url-pattern>*.JSPF</url-pattern>
+    <url-pattern>*.JSPX</url-pattern>
+    <url-pattern>*.XSP</url-pattern>
+  </servlet-mapping>
+
+  <!-- ==================================================================== -->
+  <!-- Dynamic Servlet Invoker.                                             -->
+  <!-- This servlet invokes anonymous servlets that have not been defined   -->
+  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
+  <!-- of a request passed to the envoker is treated as a servlet name for  -->
+  <!-- an existing servlet, or as a class name of a new servlet.            -->
+  <!-- This servlet is normally mapped to /servlet/*                        -->
+  <!-- This servlet support the following initParams:                       -->
+  <!--                                                                      -->
+  <!--  nonContextServlets       If false, the invoker can only load        -->
+  <!--                           servlets from the contexts classloader.    -->
+  <!--                           This is false by default and setting this  -->
+  <!--                           to true may have security implications.    -->
+  <!--                                                                      -->
+  <!--  verbose                  If true, log dynamic loads                 -->
+  <!--                                                                      -->
+  <!--  *                        All other parameters are copied to the     -->
+  <!--                           each dynamic servlet as init parameters    -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!--
+    Uncomment for dynamic invocation <servlet> <servlet-name>invoker</servlet-name> <servlet-class>org.eclipse.jetty.servlet.Invoker</servlet-class> <init-param> <param-name>verbose</param-name>
+    <param-value>false</param-value> </init-param> <init-param> <param-name>nonContextServlets</param-name> <param-value>false</param-value> </init-param> <init-param>
+    <param-name>dynamicParam</param-name> <param-value>anyValue</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>invoker</servlet-name>
+    <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
+  -->
+
+
+
+  <!-- ==================================================================== -->
+  <session-config>
+    <session-timeout>30</session-timeout>
+  </session-config>
+
+  <!-- ==================================================================== -->
+  <!-- Default MIME mappings                                                -->
+  <!-- The default MIME mappings are provided by the mime.properties        -->
+  <!-- resource in the org.eclipse.jetty.server.jar file.  Additional or modified  -->
+  <!-- mappings may be specified here                                       -->
+  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
+  <!-- UNCOMMENT TO ACTIVATE
+  <mime-mapping>
+    <extension>mysuffix</extension>
+    <mime-type>mymime/type</mime-type>
+  </mime-mapping>
+  -->
+
+  <!-- ==================================================================== -->
+  <welcome-file-list>
+    <welcome-file>index.html</welcome-file>
+    <welcome-file>index.htm</welcome-file>
+    <welcome-file>index.jsp</welcome-file>
+  </welcome-file-list>
+
+  <!-- ==================================================================== -->
+  <locale-encoding-mapping-list>
+    <locale-encoding-mapping>
+      <locale>ar</locale>
+      <encoding>ISO-8859-6</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>be</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>bg</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>ca</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>cs</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>da</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>de</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>el</locale>
+      <encoding>ISO-8859-7</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>en</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>es</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>et</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>fi</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>fr</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>hr</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>hu</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>is</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>it</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>iw</locale>
+      <encoding>ISO-8859-8</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>ja</locale>
+      <encoding>Shift_JIS</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>ko</locale>
+      <encoding>EUC-KR</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>lt</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>lv</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>mk</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>nl</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>no</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>pl</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>pt</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>ro</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>ru</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sh</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sk</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sl</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sq</locale>
+      <encoding>ISO-8859-2</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sr</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>sv</locale>
+      <encoding>ISO-8859-1</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>tr</locale>
+      <encoding>ISO-8859-9</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>uk</locale>
+      <encoding>ISO-8859-5</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>zh</locale>
+      <encoding>GB2312</encoding>
+    </locale-encoding-mapping>
+    <locale-encoding-mapping>
+      <locale>zh_TW</locale>
+      <encoding>Big5</encoding>
+    </locale-encoding-mapping>
+  </locale-encoding-mapping-list>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Disable TRACE</web-resource-name>
+      <url-pattern>/</url-pattern>
+      <http-method>TRACE</http-method>
+    </web-resource-collection>
+    <auth-constraint/>
+  </security-constraint>
+
+</web-app>
+
diff --git a/src/java/org/eclipse/jetty/client/AbstractHttpConnection.java b/src/java/org/eclipse/jetty/client/AbstractHttpConnection.java
new file mode 100644
index 0000000..0e0042c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/AbstractHttpConnection.java
@@ -0,0 +1,571 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.client.security.Authentication;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/**
+ *
+ * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
+ */
+public abstract class AbstractHttpConnection extends AbstractConnection implements Dumpable
+{
+    private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
+
+    protected HttpDestination _destination;
+    protected HttpGenerator _generator;
+    protected HttpParser _parser;
+    protected boolean _http11 = true;
+    protected int _status;
+    protected Buffer _connectionHeader;
+    protected boolean _reserved;
+
+    // The current exchange waiting for a response
+    protected volatile HttpExchange _exchange;
+    protected HttpExchange _pipeline;
+    private final Timeout.Task _idleTimeout = new ConnectionIdleTask();
+    private AtomicBoolean _idle = new AtomicBoolean(false);
+
+
+    AbstractHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
+    {
+        super(endp);
+
+        _generator = new HttpGenerator(requestBuffers,endp);
+        _parser = new HttpParser(responseBuffers,endp,new Handler());
+    }
+
+    public void setReserved (boolean reserved)
+    {
+        _reserved = reserved;
+    }
+
+    public boolean isReserved()
+    {
+        return _reserved;
+    }
+
+    public HttpDestination getDestination()
+    {
+        return _destination;
+    }
+
+    public void setDestination(HttpDestination destination)
+    {
+        _destination = destination;
+    }
+
+    public boolean send(HttpExchange ex) throws IOException
+    {
+        LOG.debug("Send {} on {}",ex,this);
+        synchronized (this)
+        {
+            if (_exchange != null)
+            {
+                if (_pipeline != null)
+                    throw new IllegalStateException(this + " PIPELINED!!!  _exchange=" + _exchange);
+                _pipeline = ex;
+                return true;
+            }
+
+            _exchange = ex;
+            _exchange.associate(this);
+
+            // The call to associate() may have closed the connection, check if it's the case
+            if (!_endp.isOpen())
+            {
+                _exchange.disassociate();
+                _exchange = null;
+                return false;
+            }
+
+            _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_COMMIT);
+
+            adjustIdleTimeout();
+
+            return true;
+        }
+    }
+
+    private void adjustIdleTimeout() throws IOException
+    {
+        // Adjusts the idle timeout in case the default or exchange timeout
+        // are greater. This is needed for long polls, where one wants an
+        // aggressive releasing of idle connections (so idle timeout is small)
+        // but still allow long polls to complete normally
+
+        long timeout = _exchange.getTimeout();
+        if (timeout <= 0)
+            timeout = _destination.getHttpClient().getTimeout();
+
+        long endPointTimeout = _endp.getMaxIdleTime();
+
+        if (timeout > 0 && timeout > endPointTimeout)
+        {
+            // Make it larger than the exchange timeout so that there are
+            // no races between the idle timeout and the exchange timeout
+            // when trying to close the endpoint
+            _endp.setMaxIdleTime(2 * (int)timeout);
+        }
+    }
+
+    public abstract Connection handle() throws IOException;
+
+
+    public boolean isIdle()
+    {
+        synchronized (this)
+        {
+            return _exchange == null;
+        }
+    }
+
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    public void onClose()
+    {
+    }
+
+    /**
+     * @throws IOException
+     */
+    protected void commitRequest() throws IOException
+    {
+        synchronized (this)
+        {
+            _status=0;
+            if (_exchange.getStatus() != HttpExchange.STATUS_WAITING_FOR_COMMIT)
+                throw new IllegalStateException();
+
+            _exchange.setStatus(HttpExchange.STATUS_SENDING_REQUEST);
+            _generator.setVersion(_exchange.getVersion());
+
+            String method=_exchange.getMethod();
+            String uri = _exchange.getRequestURI();
+            if (_destination.isProxied())
+            {
+                if (!HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
+                {
+                    boolean secure = _destination.isSecure();
+                    String host = _destination.getAddress().getHost();
+                    int port = _destination.getAddress().getPort();
+                    StringBuilder absoluteURI = new StringBuilder();
+                    absoluteURI.append(secure ? HttpSchemes.HTTPS : HttpSchemes.HTTP);
+                    absoluteURI.append("://");
+                    absoluteURI.append(host);
+                    // Avoid adding default ports
+                    if (!(secure && port == 443 || !secure && port == 80))
+                        absoluteURI.append(":").append(port);
+                    absoluteURI.append(uri);
+                    uri = absoluteURI.toString();
+                }
+                Authentication auth = _destination.getProxyAuthentication();
+                if (auth != null)
+                    auth.setCredentials(_exchange);
+            }
+
+            _generator.setRequest(method, uri);
+            _parser.setHeadResponse(HttpMethods.HEAD.equalsIgnoreCase(method));
+
+            HttpFields requestHeaders = _exchange.getRequestFields();
+            if (_exchange.getVersion() >= HttpVersions.HTTP_1_1_ORDINAL)
+            {
+                if (!requestHeaders.containsKey(HttpHeaders.HOST_BUFFER))
+                    requestHeaders.add(HttpHeaders.HOST_BUFFER,_destination.getHostHeader());
+            }
+
+            Buffer requestContent = _exchange.getRequestContent();
+            if (requestContent != null)
+            {
+                requestHeaders.putLongField(HttpHeaders.CONTENT_LENGTH, requestContent.length());
+                _generator.completeHeader(requestHeaders,false);
+                _generator.addContent(new View(requestContent),true);
+                _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+            }
+            else
+            {
+                InputStream requestContentStream = _exchange.getRequestContentSource();
+                if (requestContentStream != null)
+                {
+                    _generator.completeHeader(requestHeaders, false);
+                }
+                else
+                {
+                    requestHeaders.remove(HttpHeaders.CONTENT_LENGTH);
+                    _generator.completeHeader(requestHeaders, true);
+                    _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+                }
+            }
+        }
+    }
+
+    protected void reset() throws IOException
+    {
+        _connectionHeader = null;
+        _parser.reset();
+        _generator.reset();
+        _http11 = true;
+    }
+
+
+    private class Handler extends HttpParser.EventHandler
+    {
+        @Override
+        public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
+        {
+            // System.out.println( method.toString() + "///" + url.toString() +
+            // "///" + version.toString() );
+            // TODO validate this is acceptable, the <!DOCTYPE goop was coming
+            // out here
+            // throw new IllegalStateException();
+        }
+
+        @Override
+        public void startResponse(Buffer version, int status, Buffer reason) throws IOException
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange==null)
+            {
+                LOG.warn("No exchange for response");
+                _endp.close();
+                return;
+            }
+
+            switch(status)
+            {
+                case HttpStatus.CONTINUE_100:
+                case HttpStatus.PROCESSING_102:
+                    // TODO check if appropriate expect was sent in the request.
+                    exchange.setEventListener(new NonFinalResponseListener(exchange));
+                    break;
+
+                case HttpStatus.OK_200:
+                    // handle special case for CONNECT 200 responses
+                    if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
+                        _parser.setHeadResponse(true);
+                    break;
+            }
+
+            _http11 = HttpVersions.HTTP_1_1_BUFFER.equals(version);
+            _status=status;
+            exchange.getEventListener().onResponseStatus(version,status,reason);
+            exchange.setStatus(HttpExchange.STATUS_PARSING_HEADERS);
+
+        }
+
+        @Override
+        public void parsedHeader(Buffer name, Buffer value) throws IOException
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange!=null)
+            {
+                if (HttpHeaders.CACHE.getOrdinal(name) == HttpHeaders.CONNECTION_ORDINAL)
+                {
+                    _connectionHeader = HttpHeaderValues.CACHE.lookup(value);
+                }
+                exchange.getEventListener().onResponseHeader(name,value);
+            }
+        }
+
+        @Override
+        public void headerComplete() throws IOException
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange!=null)
+            {
+                exchange.setStatus(HttpExchange.STATUS_PARSING_CONTENT);
+                if (HttpMethods.CONNECT.equalsIgnoreCase(exchange.getMethod()))
+                    _parser.setPersistent(true);
+            }
+        }
+
+        @Override
+        public void content(Buffer ref) throws IOException
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange!=null)
+                exchange.getEventListener().onResponseContent(ref);
+        }
+
+        @Override
+        public void messageComplete(long contextLength) throws IOException
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange!=null)
+                exchange.setStatus(HttpExchange.STATUS_COMPLETED);
+        }
+
+        @Override
+        public void earlyEOF()
+        {
+            HttpExchange exchange = _exchange;
+            if (exchange!=null)
+            {
+                if (!exchange.isDone())
+                {
+                    if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
+                        exchange.getEventListener().onException(new EofException("early EOF"));
+                }
+            }
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s %s g=%s p=%s",
+                super.toString(),
+                _destination == null ? "?.?.?.?:??" : _destination.getAddress(),
+                _generator,
+                _parser);
+    }
+
+    public String toDetailString()
+    {
+        return toString() + " ex=" + _exchange + " idle for " + _idleTimeout.getAge();
+    }
+
+    public void close() throws IOException
+    {
+        //if there is a live, unfinished exchange, set its status to be
+        //excepted and wake up anyone waiting on waitForDone()
+
+        HttpExchange exchange = _exchange;
+        if (exchange != null && !exchange.isDone())
+        {
+            switch (exchange.getStatus())
+            {
+                case HttpExchange.STATUS_CANCELLED:
+                case HttpExchange.STATUS_CANCELLING:
+                case HttpExchange.STATUS_COMPLETED:
+                case HttpExchange.STATUS_EXCEPTED:
+                case HttpExchange.STATUS_EXPIRED:
+                    break;
+                case HttpExchange.STATUS_PARSING_CONTENT:
+                    if (_endp.isInputShutdown() && _parser.isState(HttpParser.STATE_EOF_CONTENT))
+                        break;
+                default:
+                    String exch= exchange.toString();
+                    String reason = _endp.isOpen()?(_endp.isInputShutdown()?"half closed: ":"local close: "):"closed: ";
+                    if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
+                        exchange.getEventListener().onException(new EofException(reason+exch));
+            }
+        }
+
+        if (_endp.isOpen())
+        {
+            _endp.close();
+            _destination.returnConnection(this, true);
+        }
+    }
+
+    public void setIdleTimeout()
+    {
+        synchronized (this)
+        {
+            if (_idle.compareAndSet(false, true))
+                _destination.getHttpClient().scheduleIdle(_idleTimeout);
+            else
+                throw new IllegalStateException();
+        }
+    }
+
+    public boolean cancelIdleTimeout()
+    {
+        synchronized (this)
+        {
+            if (_idle.compareAndSet(true, false))
+            {
+                _destination.getHttpClient().cancel(_idleTimeout);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    protected void exchangeExpired(HttpExchange exchange)
+    {
+        synchronized (this)
+        {
+            // We are expiring an exchange, but the exchange is pending
+            // Cannot reuse the connection because the reply may arrive, so close it
+            if (_exchange == exchange)
+            {
+                try
+                {
+                    _destination.returnConnection(this, true);
+                }
+                catch (IOException x)
+                {
+                    LOG.ignore(x);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.Dumpable#dump()
+     */
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.Dumpable#dump(java.lang.Appendable, java.lang.String)
+     */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        synchronized (this)
+        {
+            out.append(String.valueOf(this)).append("\n");
+            AggregateLifeCycle.dump(out,indent,Collections.singletonList(_endp));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private class ConnectionIdleTask extends Timeout.Task
+    {
+        /* ------------------------------------------------------------ */
+        @Override
+        public void expired()
+        {
+            // Connection idle, close it
+            if (_idle.compareAndSet(true, false))
+            {
+                _destination.returnIdleConnection(AbstractHttpConnection.this);
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private class NonFinalResponseListener implements HttpEventListener
+    {
+        final HttpExchange _exchange;
+        final HttpEventListener _next;
+
+        /* ------------------------------------------------------------ */
+        public NonFinalResponseListener(HttpExchange exchange)
+        {
+            _exchange=exchange;
+            _next=exchange.getEventListener();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onRequestCommitted() throws IOException
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onRequestComplete() throws IOException
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onResponseHeader(Buffer name, Buffer value) throws IOException
+        {
+            _next.onResponseHeader(name,value);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onResponseHeaderComplete() throws IOException
+        {
+            _next.onResponseHeaderComplete();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onResponseContent(Buffer content) throws IOException
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onResponseComplete() throws IOException
+        {
+            _exchange.setEventListener(_next);
+            _exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+            _parser.reset();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onConnectionFailed(Throwable ex)
+        {
+            _exchange.setEventListener(_next);
+            _next.onConnectionFailed(ex);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onException(Throwable ex)
+        {
+            _exchange.setEventListener(_next);
+            _next.onException(ex);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onExpire()
+        {
+            _exchange.setEventListener(_next);
+            _next.onExpire();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void onRetry()
+        {
+            _exchange.setEventListener(_next);
+            _next.onRetry();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/Address.java b/src/java/org/eclipse/jetty/client/Address.java
new file mode 100644
index 0000000..5ea7f04
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/Address.java
@@ -0,0 +1,96 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.net.InetSocketAddress;
+
+/**
+ * @version $Revision: 4135 $ $Date: 2008-12-02 11:57:07 +0100 (Tue, 02 Dec 2008) $
+ */
+public class Address
+{
+    private final String host;
+    private final int port;
+
+    public static Address from(String hostAndPort)
+    {
+        String host;
+        int port;
+        int colon = hostAndPort.indexOf(':');
+        if (colon >= 0)
+        {
+            host = hostAndPort.substring(0, colon);
+            port = Integer.parseInt(hostAndPort.substring(colon + 1));
+        }
+        else
+        {
+            host = hostAndPort;
+            port = 0;
+        }
+        return new Address(host, port);
+    }
+
+    public Address(String host, int port)
+    {
+        if (host == null)
+            throw new IllegalArgumentException("Host is null");
+        
+        this.host = host.trim();
+        this.port = port;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        Address that = (Address)obj;
+        if (!host.equals(that.host)) return false;
+        return port == that.port;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = host.hashCode();
+        result = 31 * result + port;
+        return result;
+    }
+
+    public String getHost()
+    {
+        return host;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public InetSocketAddress toSocketAddress()
+    {
+        return new InetSocketAddress(getHost(), getPort());
+    }
+
+    @Override
+    public String toString()
+    {
+        return host + ":" + port;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/AsyncHttpConnection.java b/src/java/org/eclipse/jetty/client/AsyncHttpConnection.java
new file mode 100644
index 0000000..8af1fea
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/AsyncHttpConnection.java
@@ -0,0 +1,269 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/* ------------------------------------------------------------ */
+/** Asynchronous Client HTTP Connection
+ */
+public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection
+{
+    private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class);
+
+    private boolean _requestComplete;
+    private Buffer _requestContentChunk;
+    private final AsyncEndPoint _asyncEndp;
+
+    AsyncHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endp)
+    {
+        super(requestBuffers,responseBuffers,endp);
+        _asyncEndp=(AsyncEndPoint)endp;
+    }
+
+    protected void reset() throws IOException
+    {
+        _requestComplete = false;
+        super.reset();
+    }
+
+    public Connection handle() throws IOException
+    {
+        Connection connection = this;
+        boolean progress=true;
+
+        try
+        {
+            boolean failed = false;
+
+            // While we are making progress and have not changed connection
+            while (progress && connection==this)
+            {
+                LOG.debug("while open={} more={} progress={}",_endp.isOpen(),_parser.isMoreInBuffer(),progress);
+
+                progress=false;
+                HttpExchange exchange=_exchange;
+
+                LOG.debug("exchange {} on {}",exchange,this);
+
+                try
+                {
+                    // Should we commit the request?
+                    if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
+                    {
+                        LOG.debug("commit {}",exchange);
+                        progress=true;
+                        commitRequest();
+                    }
+
+                    // Generate output
+                    if (_generator.isCommitted() && !_generator.isComplete())
+                    {
+                        if (_generator.flushBuffer()>0)
+                        {
+                            LOG.debug("flushed");
+                            progress=true;
+                        }
+
+                        // Is there more content to send or should we complete the generator
+                        if (_generator.isState(AbstractGenerator.STATE_CONTENT))
+                        {
+                            // Look for more content to send.
+                            if (_requestContentChunk==null)
+                                _requestContentChunk = exchange.getRequestContentChunk(null);
+
+                            if (_requestContentChunk==null)
+                            {
+                                LOG.debug("complete {}",exchange);
+                                progress=true;
+                                _generator.complete();
+                                if (exchange.getStatus() < HttpExchange.STATUS_WAITING_FOR_RESPONSE)
+                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+                            }
+                            else if (_generator.isEmpty())
+                            {
+                                LOG.debug("addChunk");
+                                progress=true;
+                                Buffer chunk=_requestContentChunk;
+                                _requestContentChunk=exchange.getRequestContentChunk(null);
+                                _generator.addContent(chunk,_requestContentChunk==null);
+                                if (_requestContentChunk==null)
+                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+                            }
+                        }
+                    }
+
+                    // Signal request completion
+                    if (_generator.isComplete() && !_requestComplete)
+                    {
+                        LOG.debug("requestComplete {}",exchange);
+                        progress=true;
+                        _requestComplete = true;
+                        exchange.getEventListener().onRequestComplete();
+                    }
+
+                    // Read any input that is available
+                    if (!_parser.isComplete() && _parser.parseAvailable())
+                    {
+                        LOG.debug("parsed {}",exchange);
+                        progress=true;
+                    }
+
+                    // Flush output
+                    _endp.flush();
+
+                    // Has any IO been done by the endpoint itself since last loop
+                    if (_asyncEndp.hasProgressed())
+                    {
+                        LOG.debug("hasProgressed {}",exchange);
+                        progress=true;
+                    }
+                }
+                catch (Throwable e)
+                {
+                    LOG.debug("Failure on " + _exchange, e);
+
+                    failed = true;
+
+                    synchronized (this)
+                    {
+                        if (exchange != null)
+                        {
+                            // Cancelling the exchange causes an exception as we close the connection,
+                            // but we don't report it as it is normal cancelling operation
+                            if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING &&
+                                    exchange.getStatus() != HttpExchange.STATUS_CANCELLED &&
+                                    !exchange.isDone())
+                            {
+                                if (exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
+                                    exchange.getEventListener().onException(e);
+                            }
+                        }
+                        else
+                        {
+                            if (e instanceof IOException)
+                                throw (IOException)e;
+                            if (e instanceof Error)
+                                throw (Error)e;
+                            if (e instanceof RuntimeException)
+                                throw (RuntimeException)e;
+                            throw new RuntimeException(e);
+                        }
+                    }
+                }
+                finally
+                {
+                    LOG.debug("finally {} on {} progress={} {}",exchange,this,progress,_endp);
+
+                    boolean complete = failed || _generator.isComplete() && _parser.isComplete();
+
+                    if (complete)
+                    {
+                        boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent();
+                        _generator.setPersistent(persistent);
+                        reset();
+                        if (persistent)
+                            _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout());
+
+                        synchronized (this)
+                        {
+                            exchange=_exchange;
+                            _exchange = null;
+
+                            // Cancel the exchange
+                            if (exchange!=null)
+                            {
+                                exchange.cancelTimeout(_destination.getHttpClient());
+
+                                // TODO should we check the exchange is done?
+                            }
+
+                            // handle switched protocols
+                            if (_status==HttpStatus.SWITCHING_PROTOCOLS_101)
+                            {
+                                Connection switched=exchange.onSwitchProtocol(_endp);
+                                if (switched!=null)
+                                {
+                                    // switched protocol!
+                                    if (_pipeline!=null)
+                                    {
+                                        _destination.send(_pipeline);
+                                    }
+                                    _pipeline = null;
+
+                                    connection=switched;
+                                }
+                            }
+
+                            // handle pipelined requests
+                            if (_pipeline!=null)
+                            {
+                                if (!persistent || connection!=this)
+                                    _destination.send(_pipeline);
+                                else
+                                    _exchange=_pipeline;
+                                _pipeline=null;
+                            }
+
+                            if (_exchange==null && !isReserved())  // TODO how do we return switched connections?
+                                _destination.returnConnection(this, !persistent);
+                        }
+
+                    }
+                }
+            }
+        }
+        finally
+        {
+            _parser.returnBuffers();
+            _generator.returnBuffers();
+            LOG.debug("unhandle {} on {}",_exchange,_endp);
+        }
+
+        return connection;
+    }
+
+    public void onInputShutdown() throws IOException
+    {
+        if (_generator.isIdle())
+            _endp.shutdownOutput();
+    }
+
+    @Override
+    public boolean send(HttpExchange ex) throws IOException
+    {
+        boolean sent=super.send(ex);
+        if (sent)
+            _asyncEndp.asyncDispatch();
+        return sent;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/BlockingHttpConnection.java b/src/java/org/eclipse/jetty/client/BlockingHttpConnection.java
new file mode 100644
index 0000000..94d163b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/BlockingHttpConnection.java
@@ -0,0 +1,314 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Blocking HTTP Connection
+ */
+public class BlockingHttpConnection extends AbstractHttpConnection
+{
+    private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class);
+
+    private boolean _requestComplete;
+    private Buffer _requestContentChunk;
+    private boolean _expired=false;
+
+    BlockingHttpConnection(Buffers requestBuffers, Buffers responseBuffers, EndPoint endPoint)
+    {
+        super(requestBuffers, responseBuffers, endPoint);
+    }
+
+    protected void reset() throws IOException
+    {
+        _requestComplete = false;
+        _expired = false;
+        super.reset();
+    }
+    
+    
+    @Override
+    protected void exchangeExpired(HttpExchange exchange)
+    {
+        synchronized (this)
+        {
+           super.exchangeExpired(exchange);
+           _expired = true;
+           this.notifyAll();
+        }
+    }
+    
+    
+
+    @Override
+    public void onIdleExpired(long idleForMs)
+    {
+        try
+        {
+            LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp);
+            _expired = true;
+            _endp.close();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+
+            try
+            {
+                _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+        }
+
+        synchronized(this)
+        {
+            this.notifyAll();
+        }
+    }
+
+    @Override
+    public Connection handle() throws IOException
+    {
+        Connection connection = this;
+
+        try
+        {
+            boolean failed = false;
+
+
+            // While we are making progress and have not changed connection
+            while (_endp.isOpen() && connection==this)
+            {
+                LOG.debug("open={} more={}",_endp.isOpen(),_parser.isMoreInBuffer());
+
+                HttpExchange exchange;
+                synchronized (this)
+                {
+                    exchange=_exchange;
+                    while (exchange == null)
+                    {
+                        try
+                        {
+                            this.wait();
+                            exchange=_exchange;
+                            if (_expired)
+                            {
+                                failed = true;
+                                throw new InterruptedException();
+                            }
+
+                        }
+                        catch (InterruptedException e)
+                        {
+                            throw new InterruptedIOException();
+                        }
+                    }
+                }
+                LOG.debug("exchange {}",exchange);
+
+                try
+                {
+                    // Should we commit the request?
+                    if (!_generator.isCommitted() && exchange!=null && exchange.getStatus() == HttpExchange.STATUS_WAITING_FOR_COMMIT)
+                    {
+                        LOG.debug("commit");
+                        commitRequest();
+                    }
+
+                    // Generate output
+                    while (_generator.isCommitted() && !_generator.isComplete())
+                    {
+                        if (_generator.flushBuffer()>0)
+                        {
+                            LOG.debug("flushed");
+                        }
+
+                        // Is there more content to send or should we complete the generator
+                        if (_generator.isState(AbstractGenerator.STATE_CONTENT))
+                        {
+                            // Look for more content to send.
+                            if (_requestContentChunk==null)
+                                _requestContentChunk = exchange.getRequestContentChunk(null);
+
+                            if (_requestContentChunk==null)
+                            {
+                                LOG.debug("complete");
+                                _generator.complete();
+                            }
+                            else if (_generator.isEmpty())
+                            {
+                                LOG.debug("addChunk");
+                                Buffer chunk=_requestContentChunk;
+                                _requestContentChunk=exchange.getRequestContentChunk(null);
+                                _generator.addContent(chunk,_requestContentChunk==null);
+                                if (_requestContentChunk==null)
+                                    exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
+                            }
+                        }
+                    }
+
+                    // Signal request completion
+                    if (_generator.isComplete() && !_requestComplete)
+                    {
+                        LOG.debug("requestComplete");
+                        _requestComplete = true;
+                        exchange.getEventListener().onRequestComplete();
+                    }
+
+                    // Read any input that is available
+                    if (!_parser.isComplete() && _parser.parseAvailable())
+                    {
+                        LOG.debug("parsed");
+                    }
+
+                    // Flush output
+                    _endp.flush();
+                }
+                catch (Throwable e)
+                {
+                    LOG.debug("Failure on " + _exchange, e);
+
+                    failed = true;
+
+                    synchronized (this)
+                    {
+                        if (exchange != null)
+                        {
+                            // Cancelling the exchange causes an exception as we close the connection,
+                            // but we don't report it as it is normal cancelling operation
+                            if (exchange.getStatus() != HttpExchange.STATUS_CANCELLING &&
+                                    exchange.getStatus() != HttpExchange.STATUS_CANCELLED &&
+                                    !exchange.isDone())
+                            {
+                                if(exchange.setStatus(HttpExchange.STATUS_EXCEPTED))
+                                    exchange.getEventListener().onException(e);
+                            }
+                        }
+                        else
+                        {
+                            if (e instanceof IOException)
+                                throw (IOException)e;
+                            if (e instanceof Error)
+                                throw (Error)e;
+                            if (e instanceof RuntimeException)
+                                throw (RuntimeException)e;
+                            throw new RuntimeException(e);
+                        }
+                    }
+                }
+                finally
+                {
+                    LOG.debug("{} {}",_generator, _parser);
+                    LOG.debug("{}",_endp);
+
+                    boolean complete = failed || _generator.isComplete() && _parser.isComplete();
+
+                    if (complete)
+                    {
+                        boolean persistent = !failed && _parser.isPersistent() && _generator.isPersistent();
+                        _generator.setPersistent(persistent);
+                        reset();
+                        if (persistent)
+                            _endp.setMaxIdleTime((int)_destination.getHttpClient().getIdleTimeout());
+
+                        synchronized (this)
+                        {
+                            exchange=_exchange;
+                            _exchange = null;
+
+                            // Cancel the exchange
+                            if (exchange!=null)
+                            {
+                                exchange.cancelTimeout(_destination.getHttpClient());
+
+                                // TODO should we check the exchange is done?
+                            }
+
+                            // handle switched protocols
+                            if (_status==HttpStatus.SWITCHING_PROTOCOLS_101)
+                            {
+                                Connection switched=exchange.onSwitchProtocol(_endp);
+                                if (switched!=null)
+                                    connection=switched;
+                                {
+                                    // switched protocol!
+                                    _pipeline = null;
+                                    if (_pipeline!=null)
+                                        _destination.send(_pipeline);
+                                    _pipeline = null;
+
+                                    connection=switched;
+                                }
+                            }
+
+                            // handle pipelined requests
+                            if (_pipeline!=null)
+                            {
+                                if (!persistent || connection!=this)
+                                    _destination.send(_pipeline);
+                                else
+                                    _exchange=_pipeline;
+                                _pipeline=null;
+                            }
+
+                            if (_exchange==null && !isReserved())  // TODO how do we return switched connections?
+                                _destination.returnConnection(this, !persistent);
+                        }
+                    }
+                }
+            }
+        }
+        finally
+        {
+            _parser.returnBuffers();
+            _generator.returnBuffers();
+        }
+
+        return connection;
+    }
+
+    @Override
+    public boolean send(HttpExchange ex) throws IOException
+    {
+        boolean sent=super.send(ex);
+        if (sent)
+        {
+            synchronized (this)
+            {
+                notifyAll();
+            }
+        }
+        return sent;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/CachedExchange.java b/src/java/org/eclipse/jetty/client/CachedExchange.java
new file mode 100644
index 0000000..2643399
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/CachedExchange.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.Buffer;
+
+/**
+ * An exchange that retains response status and response headers for later use.
+ */
+public class CachedExchange extends HttpExchange
+{
+    private final HttpFields _responseFields;
+    private volatile int _responseStatus;
+
+    /**
+     * Creates a new CachedExchange.
+     *
+     * @param cacheHeaders true to cache response headers, false to not cache them
+     */
+    public CachedExchange(boolean cacheHeaders)
+    {
+        _responseFields = cacheHeaders ? new HttpFields() : null;
+    }
+
+    public synchronized int getResponseStatus()
+    {
+        if (getStatus() < HttpExchange.STATUS_PARSING_HEADERS)
+            throw new IllegalStateException("Response not received yet");
+        return _responseStatus;
+    }
+
+    public synchronized HttpFields getResponseFields()
+    {
+        if (getStatus() < HttpExchange.STATUS_PARSING_CONTENT)
+            throw new IllegalStateException("Headers not completely received yet");
+        return _responseFields;
+    }
+
+    @Override
+    protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        _responseStatus = status;
+        super.onResponseStatus(version, status, reason);
+    }
+
+    @Override
+    protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException
+    {
+        if (_responseFields != null)
+        {
+            _responseFields.add(name, value.asImmutableBuffer());
+        }
+        
+        super.onResponseHeader(name, value);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/ContentExchange.java b/src/java/org/eclipse/jetty/client/ContentExchange.java
new file mode 100644
index 0000000..4cbbe9c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/ContentExchange.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * A exchange that retains response content for later use.
+ */
+public class ContentExchange extends CachedExchange
+{
+    private int _bufferSize = 4096;
+    private String _encoding = "utf-8";
+    private ByteArrayOutputStream _responseContent;
+    private File _fileForUpload;
+
+    public ContentExchange()
+    {
+        super(false);
+    }
+
+    public ContentExchange(boolean cacheFields)
+    {
+        super(cacheFields);
+    }
+
+    public synchronized String getResponseContent() throws UnsupportedEncodingException
+    {
+        if (_responseContent != null)
+            return _responseContent.toString(_encoding);
+        return null;
+    }
+
+    public synchronized byte[] getResponseContentBytes()
+    {
+        if (_responseContent != null)
+            return _responseContent.toByteArray();
+        return null;
+    }
+
+    @Override
+    protected synchronized void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        if (_responseContent!=null)
+            _responseContent.reset();
+        super.onResponseStatus(version,status,reason);
+    }
+
+    @Override
+    protected synchronized void onResponseHeader(Buffer name, Buffer value) throws IOException
+    {
+        super.onResponseHeader(name, value);
+        int header = HttpHeaders.CACHE.getOrdinal(name);
+        switch (header)
+        {
+            case HttpHeaders.CONTENT_LENGTH_ORDINAL:
+                _bufferSize = BufferUtil.toInt(value);
+                break;
+            case HttpHeaders.CONTENT_TYPE_ORDINAL:
+                String mime = StringUtil.asciiToLowerCase(value.toString());
+                int i = mime.indexOf("charset=");
+                if (i > 0)
+                {
+                    _encoding = mime.substring(i + 8);
+                    i = _encoding.indexOf(';');
+                    if (i > 0)
+                        _encoding = _encoding.substring(0, i);
+                }
+                break;
+        }
+    }
+
+    @Override
+    protected synchronized void onResponseContent(Buffer content) throws IOException
+    {
+        super.onResponseContent(content);
+        if (_responseContent == null)
+            _responseContent = new ByteArrayOutputStream(_bufferSize);
+        content.writeTo(_responseContent);
+    }
+
+    @Override
+    protected synchronized void onRetry() throws IOException
+    {
+        if (_fileForUpload != null)
+        {
+            setRequestContent(null);
+            setRequestContentSource(getInputStream());
+        }
+        else
+            super.onRetry();
+    }
+
+    private synchronized InputStream getInputStream() throws IOException
+    {
+        return new FileInputStream(_fileForUpload);
+    }
+
+    public synchronized File getFileForUpload()
+    {
+        return _fileForUpload;
+    }
+
+    public synchronized void setFileForUpload(File fileForUpload) throws IOException
+    {
+        this._fileForUpload = fileForUpload;
+        setRequestContentSource(getInputStream());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/HttpClient.java b/src/java/org/eclipse/jetty/client/HttpClient.java
new file mode 100644
index 0000000..80f5536
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/HttpClient.java
@@ -0,0 +1,911 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.net.ssl.SSLContext;
+
+import org.eclipse.jetty.client.security.Authentication;
+import org.eclipse.jetty.client.security.RealmResolver;
+import org.eclipse.jetty.client.security.SecurityListener;
+import org.eclipse.jetty.http.HttpBuffers;
+import org.eclipse.jetty.http.HttpBuffersImpl;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/**
+ * Http Client.
+ * <p/>
+ * HttpClient is the main active component of the client API implementation.
+ * It is the opposite of the Connectors in standard Jetty, in that it listens
+ * for responses rather than requests.   Just like the connectors, there is a
+ * blocking socket version and a non-blocking NIO version (implemented as nested classes
+ * selected by {@link #setConnectorType(int)}).
+ * <p/>
+ * The an instance of {@link HttpExchange} is passed to the {@link #send(HttpExchange)} method
+ * to send a request.  The exchange contains both the headers and content (source) of the request
+ * plus the callbacks to handle responses.   A HttpClient can have many exchanges outstanding
+ * and they may be queued on the {@link HttpDestination} waiting for a {@link AbstractHttpConnection},
+ * queued in the {@link AbstractHttpConnection} waiting to be transmitted or pipelined on the actual
+ * TCP/IP connection waiting for a response.
+ * <p/>
+ * The {@link HttpDestination} class is an aggregation of {@link AbstractHttpConnection}s for the
+ * same host, port and protocol.   A destination may limit the number of connections
+ * open and they provide a pool of open connections that may be reused.   Connections may also
+ * be allocated from a destination, so that multiple request sources are not multiplexed
+ * over the same connection.
+ *
+ * @see HttpExchange
+ * @see HttpDestination
+ */
+public class HttpClient extends AggregateLifeCycle implements HttpBuffers, Attributes, Dumpable
+{
+    public static final int CONNECTOR_SOCKET = 0;
+    public static final int CONNECTOR_SELECT_CHANNEL = 2;
+
+    private int _connectorType = CONNECTOR_SELECT_CHANNEL;
+    private boolean _useDirectBuffers = true;
+    private boolean _connectBlocking = true;
+    private int _maxConnectionsPerAddress = Integer.MAX_VALUE;
+    private int _maxQueueSizePerAddress = Integer.MAX_VALUE;
+    private ConcurrentMap<Address, HttpDestination> _destinations = new ConcurrentHashMap<Address, HttpDestination>();
+    ThreadPool _threadPool;
+    Connector _connector;
+    private long _idleTimeout = 20000;
+    private long _timeout = 320000;
+    private int _connectTimeout = 75000;
+    private Timeout _timeoutQ = new Timeout();
+    private Timeout _idleTimeoutQ = new Timeout();
+    private Address _proxy;
+    private Authentication _proxyAuthentication;
+    private Set<String> _noProxy;
+    private int _maxRetries = 3;
+    private int _maxRedirects = 20;
+    private LinkedList<String> _registeredListeners;
+
+    private final SslContextFactory _sslContextFactory;
+
+    private RealmResolver _realmResolver;
+
+    private AttributesMap _attributes=new AttributesMap();
+
+    private final HttpBuffersImpl _buffers= new HttpBuffersImpl();
+
+    /* ------------------------------------------------------------------------------- */
+    private void setBufferTypes()
+    {
+        if (_connectorType==CONNECTOR_SOCKET)
+        {
+            _buffers.setRequestBufferType(Type.BYTE_ARRAY);
+            _buffers.setRequestHeaderType(Type.BYTE_ARRAY);
+            _buffers.setResponseBufferType(Type.BYTE_ARRAY);
+            _buffers.setResponseHeaderType(Type.BYTE_ARRAY);
+        }
+        else
+        {
+            _buffers.setRequestBufferType(Type.DIRECT);
+            _buffers.setRequestHeaderType(_useDirectBuffers?Type.DIRECT:Type.INDIRECT);
+            _buffers.setResponseBufferType(Type.DIRECT);
+            _buffers.setResponseHeaderType(_useDirectBuffers?Type.DIRECT:Type.INDIRECT);
+        }
+
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpClient()
+    {
+        this(new SslContextFactory());
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpClient(SslContextFactory sslContextFactory)
+    {
+        _sslContextFactory = sslContextFactory;
+        addBean(_sslContextFactory);
+        addBean(_buffers);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @return True if connects will be in blocking mode.
+     */
+    public boolean isConnectBlocking()
+    {
+        return _connectBlocking;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @param connectBlocking True if connects will be in blocking mode.
+     */
+    public void setConnectBlocking(boolean connectBlocking)
+    {
+        _connectBlocking = connectBlocking;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void send(HttpExchange exchange) throws IOException
+    {
+        boolean ssl = HttpSchemes.HTTPS_BUFFER.equalsIgnoreCase(exchange.getScheme());
+        HttpDestination destination = getDestination(exchange.getAddress(), ssl);
+        destination.send(exchange);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the threadpool
+     */
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the ThreadPool.
+     * The threadpool passed is added via {@link #addBean(Object)} so that
+     * it's lifecycle may be managed as a {@link AggregateLifeCycle}.
+     * @param threadPool the threadPool to set
+     */
+    public void setThreadPool(ThreadPool threadPool)
+    {
+        removeBean(_threadPool);
+        _threadPool = threadPool;
+        addBean(_threadPool);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @return Attribute associated with client
+     */
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return names of attributes associated with client
+     */
+    public Enumeration getAttributeNames()
+    {
+        return _attributes.getAttributeNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     */
+    public void removeAttribute(String name)
+    {
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set an attribute on the HttpClient.
+     * Attributes are not used by the client, but are provided for
+     * so that users of a shared HttpClient may share other structures.
+     * @param name
+     * @param attribute
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _attributes.setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearAttributes()
+    {
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpDestination getDestination(Address remote, boolean ssl) throws IOException
+    {
+        return getDestination(remote, ssl, getSslContextFactory());
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public HttpDestination getDestination(Address remote, boolean ssl, SslContextFactory sslContextFactory) throws IOException
+    {
+        if (remote == null)
+            throw new UnknownHostException("Remote socket address cannot be null.");
+
+        HttpDestination destination = _destinations.get(remote);
+        if (destination == null)
+        {
+            destination = new HttpDestination(this, remote, ssl, sslContextFactory);
+            if (_proxy != null && (_noProxy == null || !_noProxy.contains(remote.getHost())))
+            {
+                destination.setProxy(_proxy);
+                if (_proxyAuthentication != null)
+                    destination.setProxyAuthentication(_proxyAuthentication);
+            }
+            HttpDestination other =_destinations.putIfAbsent(remote, destination);
+            if (other!=null)
+                destination=other;
+        }
+        return destination;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void schedule(Timeout.Task task)
+    {
+        _timeoutQ.schedule(task);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void schedule(Timeout.Task task, long timeout)
+    {
+        _timeoutQ.schedule(task, timeout - _timeoutQ.getDuration());
+    }
+
+    /* ------------------------------------------------------------ */
+    public void scheduleIdle(Timeout.Task task)
+    {
+        _idleTimeoutQ.schedule(task);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void cancel(Timeout.Task task)
+    {
+        task.cancel();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get whether the connector can use direct NIO buffers.
+     */
+    public boolean getUseDirectBuffers()
+    {
+        return _useDirectBuffers;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set a RealmResolver for client Authentication.
+     * If a realmResolver is set, then the HttpDestinations created by
+     * this client will instantiate a {@link SecurityListener} so that
+     * BASIC and DIGEST authentication can be performed.
+     * @param resolver
+     */
+    public void setRealmResolver(RealmResolver resolver)
+    {
+        _realmResolver = resolver;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the SecurityRealmResolver reg_realmResolveristered with the HttpClient or null
+     *
+     * @return the SecurityRealmResolver reg_realmResolveristered with the HttpClient or null
+     */
+    public RealmResolver getRealmResolver()
+    {
+        return _realmResolver;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean hasRealms()
+    {
+        return _realmResolver == null ? false : true;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Registers a listener that can listen to the stream of execution between the client and the
+     * server and influence events.  Sequential calls to the method wrapper sequentially wrap the preceding
+     * listener in a delegation model.
+     * <p/>
+     * NOTE: the SecurityListener is a special listener which doesn't need to be added via this
+     * mechanic, if you register security realms then it will automatically be added as the top listener of the
+     * delegation stack.
+     *
+     * @param listenerClass
+     */
+    public void registerListener(String listenerClass)
+    {
+        if (_registeredListeners == null)
+        {
+            _registeredListeners = new LinkedList<String>();
+        }
+        _registeredListeners.add(listenerClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    public LinkedList<String> getRegisteredListeners()
+    {
+        return _registeredListeners;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set to use NIO direct buffers.
+     *
+     * @param direct If True (the default), the connector can use NIO direct
+     *               buffers. Some JVMs have memory management issues (bugs) with
+     *               direct buffers.
+     */
+    public void setUseDirectBuffers(boolean direct)
+    {
+        _useDirectBuffers = direct;
+        setBufferTypes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the type of connector (socket, blocking or select) in use.
+     */
+    public int getConnectorType()
+    {
+        return _connectorType;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setConnectorType(int connectorType)
+    {
+        this._connectorType = connectorType;
+        setBufferTypes();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxConnectionsPerAddress()
+    {
+        return _maxConnectionsPerAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
+    {
+        _maxConnectionsPerAddress = maxConnectionsPerAddress;
+    }
+
+    public int getMaxQueueSizePerAddress()
+    {
+        return _maxQueueSizePerAddress;
+    }
+
+    public void setMaxQueueSizePerAddress(int maxQueueSizePerAddress)
+    {
+        this._maxQueueSizePerAddress = maxQueueSizePerAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        setBufferTypes();
+
+        _timeoutQ.setDuration(_timeout);
+        _timeoutQ.setNow();
+        _idleTimeoutQ.setDuration(_idleTimeout);
+        _idleTimeoutQ.setNow();
+
+        if (_threadPool==null)
+        {
+            QueuedThreadPool pool = new LocalQueuedThreadPool();
+            pool.setMaxThreads(16);
+            pool.setDaemon(true);
+            pool.setName("HttpClient");
+            _threadPool = pool;
+            addBean(_threadPool,true);
+        }
+
+        _connector=(_connectorType == CONNECTOR_SELECT_CHANNEL)?new SelectConnector(this):new SocketConnector(this);
+        addBean(_connector,true);
+
+        super.doStart();
+
+        _threadPool.dispatch(new Runnable()
+        {
+            public void run()
+            {
+                while (isRunning())
+                {
+                    _timeoutQ.tick(System.currentTimeMillis());
+                    _idleTimeoutQ.tick(_timeoutQ.getNow());
+                    try
+                    {
+                        Thread.sleep(200);
+                    }
+                    catch (InterruptedException ignored)
+                    {
+                    }
+                }
+            }
+        });
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        for (HttpDestination destination : _destinations.values())
+            destination.close();
+
+        _timeoutQ.cancelAll();
+        _idleTimeoutQ.cancelAll();
+
+        super.doStop();
+
+        if (_threadPool instanceof LocalQueuedThreadPool)
+        {
+            removeBean(_threadPool);
+            _threadPool = null;
+        }
+
+        removeBean(_connector);
+    }
+
+    /* ------------------------------------------------------------ */
+    interface Connector extends LifeCycle
+    {
+        public void startConnection(HttpDestination destination) throws IOException;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * if a keystore location has been provided then client will attempt to use it as the keystore,
+     * otherwise we simply ignore certificates and run with a loose ssl context.
+     *
+     * @return the SSL context
+     */
+    protected SSLContext getSSLContext()
+    {
+        return _sslContextFactory.getSslContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the instance of SslContextFactory associated with the client
+     */
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed.
+     */
+    public long getIdleTimeout()
+    {
+        return _idleTimeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param ms the period in milliseconds a {@link AbstractHttpConnection} can be idle for before it is closed.
+     */
+    public void setIdleTimeout(long ms)
+    {
+        _idleTimeout = ms;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in ms that an exchange will wait for a response from the server.
+     * @deprecated use {@link #getTimeout()} instead.
+     */
+    @Deprecated
+    public int getSoTimeout()
+    {
+        return Long.valueOf(getTimeout()).intValue();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @deprecated use {@link #setTimeout(long)} instead.
+     * @param timeout the period in ms that an exchange will wait for a response from the server.
+     */
+    @Deprecated
+    public void setSoTimeout(int timeout)
+    {
+        setTimeout(timeout);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in ms that an exchange will wait for a response from the server.
+     */
+    public long getTimeout()
+    {
+        return _timeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param timeout the period in ms that an exchange will wait for a response from the server.
+     */
+    public void setTimeout(long timeout)
+    {
+        _timeout = timeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in ms before timing out an attempt to connect
+     */
+    public int getConnectTimeout()
+    {
+        return _connectTimeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param connectTimeout the period in ms before timing out an attempt to connect
+     */
+    public void setConnectTimeout(int connectTimeout)
+    {
+        this._connectTimeout = connectTimeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Address getProxy()
+    {
+        return _proxy;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setProxy(Address proxy)
+    {
+        this._proxy = proxy;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Authentication getProxyAuthentication()
+    {
+        return _proxyAuthentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setProxyAuthentication(Authentication authentication)
+    {
+        _proxyAuthentication = authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isProxied()
+    {
+        return this._proxy != null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Set<String> getNoProxy()
+    {
+        return _noProxy;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setNoProxy(Set<String> noProxyAddresses)
+    {
+        _noProxy = noProxyAddresses;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int maxRetries()
+    {
+        return _maxRetries;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxRetries(int retries)
+    {
+        _maxRetries = retries;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int maxRedirects()
+    {
+        return _maxRedirects;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxRedirects(int redirects)
+    {
+        _maxRedirects = redirects;
+    }
+
+    public int getRequestBufferSize()
+    {
+        return _buffers.getRequestBufferSize();
+    }
+
+    public void setRequestBufferSize(int requestBufferSize)
+    {
+        _buffers.setRequestBufferSize(requestBufferSize);
+    }
+
+    public int getRequestHeaderSize()
+    {
+        return _buffers.getRequestHeaderSize();
+    }
+
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _buffers.setRequestHeaderSize(requestHeaderSize);
+    }
+
+    public int getResponseBufferSize()
+    {
+        return _buffers.getResponseBufferSize();
+    }
+
+    public void setResponseBufferSize(int responseBufferSize)
+    {
+        _buffers.setResponseBufferSize(responseBufferSize);
+    }
+
+    public int getResponseHeaderSize()
+    {
+        return _buffers.getResponseHeaderSize();
+    }
+
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _buffers.setResponseHeaderSize(responseHeaderSize);
+    }
+
+    public Type getRequestBufferType()
+    {
+        return _buffers.getRequestBufferType();
+    }
+
+    public Type getRequestHeaderType()
+    {
+        return _buffers.getRequestHeaderType();
+    }
+
+    public Type getResponseBufferType()
+    {
+        return _buffers.getResponseBufferType();
+    }
+
+    public Type getResponseHeaderType()
+    {
+        return _buffers.getResponseHeaderType();
+    }
+
+    public void setRequestBuffers(Buffers requestBuffers)
+    {
+        _buffers.setRequestBuffers(requestBuffers);
+    }
+
+    public void setResponseBuffers(Buffers responseBuffers)
+    {
+        _buffers.setResponseBuffers(responseBuffers);
+    }
+
+    public Buffers getRequestBuffers()
+    {
+        return _buffers.getRequestBuffers();
+    }
+
+    public Buffers getResponseBuffers()
+    {
+        return _buffers.getResponseBuffers();
+    }
+
+    public void setMaxBuffers(int maxBuffers)
+    {
+        _buffers.setMaxBuffers(maxBuffers);
+    }
+
+    public int getMaxBuffers()
+    {
+        return _buffers.getMaxBuffers();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getTrustStoreLocation()
+    {
+        return _sslContextFactory.getTrustStore();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setTrustStoreLocation(String trustStoreLocation)
+    {
+        _sslContextFactory.setTrustStore(trustStoreLocation);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public InputStream getTrustStoreInputStream()
+    {
+        return _sslContextFactory.getTrustStoreInputStream();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setTrustStoreInputStream(InputStream trustStoreInputStream)
+    {
+        _sslContextFactory.setTrustStoreInputStream(trustStoreInputStream);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getKeyStoreLocation()
+    {
+        return _sslContextFactory.getKeyStorePath();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setKeyStoreLocation(String keyStoreLocation)
+    {
+        _sslContextFactory.setKeyStorePath(keyStoreLocation);
+    }
+
+    @Deprecated
+    public InputStream getKeyStoreInputStream()
+    {
+        return _sslContextFactory.getKeyStoreInputStream();
+    }
+
+    @Deprecated
+    public void setKeyStoreInputStream(InputStream keyStoreInputStream)
+    {
+        _sslContextFactory.setKeyStoreInputStream(keyStoreInputStream);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setKeyStorePassword(String keyStorePassword)
+    {
+        _sslContextFactory.setKeyStorePassword(keyStorePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setKeyManagerPassword(String keyManagerPassword)
+    {
+        _sslContextFactory.setKeyManagerPassword(keyManagerPassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setTrustStorePassword(String trustStorePassword)
+    {
+        _sslContextFactory.setTrustStorePassword(trustStorePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getKeyStoreType()
+    {
+        return _sslContextFactory.getKeyStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setKeyStoreType(String keyStoreType)
+    {
+        _sslContextFactory.setKeyStoreType(keyStoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getTrustStoreType()
+    {
+        return _sslContextFactory.getTrustStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setTrustStoreType(String trustStoreType)
+    {
+        _sslContextFactory.setTrustStoreType(trustStoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getKeyManagerAlgorithm()
+    {
+        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setKeyManagerAlgorithm(String keyManagerAlgorithm)
+    {
+        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(keyManagerAlgorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getTrustManagerAlgorithm()
+    {
+        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setTrustManagerAlgorithm(String trustManagerAlgorithm)
+    {
+        _sslContextFactory.setTrustManagerFactoryAlgorithm(trustManagerAlgorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getProtocol()
+    {
+        return _sslContextFactory.getProtocol();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setProtocol(String protocol)
+    {
+        _sslContextFactory.setProtocol(protocol);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getProvider()
+    {
+        return _sslContextFactory.getProvider();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setProvider(String provider)
+    {
+        _sslContextFactory.setProvider(provider);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getSecureRandomAlgorithm()
+    {
+        return _sslContextFactory.getSecureRandomAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public void setSecureRandomAlgorithm(String secureRandomAlgorithm)
+    {
+        _sslContextFactory.setSecureRandomAlgorithm(secureRandomAlgorithm);
+    }
+
+    private static class LocalQueuedThreadPool extends QueuedThreadPool
+    {
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/HttpDestination.java b/src/java/org/eclipse/jetty/client/HttpDestination.java
new file mode 100644
index 0000000..4e551cd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/HttpDestination.java
@@ -0,0 +1,751 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.eclipse.jetty.client.HttpClient.Connector;
+import org.eclipse.jetty.client.security.Authentication;
+import org.eclipse.jetty.client.security.SecurityListener;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/**
+ * @version $Revision: 879 $ $Date: 2009-09-11 16:13:28 +0200 (Fri, 11 Sep 2009) $
+ */
+public class HttpDestination implements Dumpable
+{
+    private static final Logger LOG = Log.getLogger(HttpDestination.class);
+
+    private final List<HttpExchange> _exchanges = new LinkedList<HttpExchange>();
+    private final List<AbstractHttpConnection> _connections = new LinkedList<AbstractHttpConnection>();
+    private final BlockingQueue<Object> _reservedConnections = new ArrayBlockingQueue<Object>(10, true);
+    private final List<AbstractHttpConnection> _idleConnections = new ArrayList<AbstractHttpConnection>();
+    private final HttpClient _client;
+    private final Address _address;
+    private final boolean _ssl;
+    private final SslContextFactory _sslContextFactory;
+    private final ByteArrayBuffer _hostHeader;
+    private volatile int _maxConnections;
+    private volatile int _maxQueueSize;
+    private int _pendingConnections = 0;
+    private int _pendingReservedConnections = 0;
+    private volatile Address _proxy;
+    private Authentication _proxyAuthentication;
+    private PathMap _authorizations;
+    private List<HttpCookie> _cookies;
+
+    HttpDestination(HttpClient client, Address address, boolean ssl, SslContextFactory sslContextFactory)
+    {
+        _client = client;
+        _address = address;
+        _ssl = ssl;
+        _sslContextFactory = sslContextFactory;
+        _maxConnections = _client.getMaxConnectionsPerAddress();
+        _maxQueueSize = _client.getMaxQueueSizePerAddress();
+        String addressString = address.getHost();
+        if (address.getPort() != (_ssl ? 443 : 80))
+            addressString += ":" + address.getPort();
+        _hostHeader = new ByteArrayBuffer(addressString);
+    }
+
+    public HttpClient getHttpClient()
+    {
+        return _client;
+    }
+
+    public Address getAddress()
+    {
+        return _address;
+    }
+
+    public boolean isSecure()
+    {
+        return _ssl;
+    }
+
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    public Buffer getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    public int getMaxConnections()
+    {
+        return _maxConnections;
+    }
+
+    public void setMaxConnections(int maxConnections)
+    {
+        this._maxConnections = maxConnections;
+    }
+
+    public int getMaxQueueSize()
+    {
+        return _maxQueueSize;
+    }
+
+    public void setMaxQueueSize(int maxQueueSize)
+    {
+        this._maxQueueSize = maxQueueSize;
+    }
+
+    public int getConnections()
+    {
+        synchronized (this)
+        {
+            return _connections.size();
+        }
+    }
+
+    public int getIdleConnections()
+    {
+        synchronized (this)
+        {
+            return _idleConnections.size();
+        }
+    }
+
+    public void addAuthorization(String pathSpec, Authentication authorization)
+    {
+        synchronized (this)
+        {
+            if (_authorizations == null)
+                _authorizations = new PathMap();
+            _authorizations.put(pathSpec, authorization);
+        }
+
+        // TODO query and remove methods
+    }
+
+    public void addCookie(HttpCookie cookie)
+    {
+        synchronized (this)
+        {
+            if (_cookies == null)
+                _cookies = new ArrayList<HttpCookie>();
+            _cookies.add(cookie);
+        }
+
+        // TODO query, remove and age methods
+    }
+
+    /**
+     * Get a connection. We either get an idle connection if one is available, or
+     * we make a new connection, if we have not yet reached maxConnections. If we
+     * have reached maxConnections, we wait until the number reduces.
+     *
+     * @param timeout max time prepared to block waiting to be able to get a connection
+     * @return a HttpConnection for this destination
+     * @throws IOException if an I/O error occurs
+     */
+    private AbstractHttpConnection getConnection(long timeout) throws IOException
+    {
+        AbstractHttpConnection connection = null;
+
+        while ((connection == null) && (connection = getIdleConnection()) == null && timeout > 0)
+        {
+            boolean startConnection = false;
+            synchronized (this)
+            {
+                int totalConnections = _connections.size() + _pendingConnections;
+                if (totalConnections < _maxConnections)
+                {
+                    _pendingReservedConnections++;
+                    startConnection = true;
+                }
+            }
+
+            if (startConnection)
+            {
+                startNewConnection();
+                try
+                {
+                    Object o = _reservedConnections.take();
+                    if (o instanceof AbstractHttpConnection)
+                    {
+                        connection = (AbstractHttpConnection)o;
+                    }
+                    else
+                        throw (IOException)o;
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+            else
+            {
+                try
+                {
+                    Thread.currentThread();
+                    Thread.sleep(200);
+                    timeout -= 200;
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+        return connection;
+    }
+
+    public AbstractHttpConnection reserveConnection(long timeout) throws IOException
+    {
+        AbstractHttpConnection connection = getConnection(timeout);
+        if (connection != null)
+            connection.setReserved(true);
+        return connection;
+    }
+
+    public AbstractHttpConnection getIdleConnection() throws IOException
+    {
+        AbstractHttpConnection connection = null;
+        while (true)
+        {
+            synchronized (this)
+            {
+                if (connection != null)
+                {
+                    _connections.remove(connection);
+                    connection.close();
+                    connection = null;
+                }
+                if (_idleConnections.size() > 0)
+                    connection = _idleConnections.remove(_idleConnections.size() - 1);
+            }
+
+            if (connection == null)
+            {
+                return null;
+            }
+
+            // Check if the connection was idle,
+            // but it expired just a moment ago
+            if (connection.cancelIdleTimeout())
+            {
+                return connection;
+            }
+        }
+    }
+
+    protected void startNewConnection()
+    {
+        try
+        {
+            synchronized (this)
+            {
+                _pendingConnections++;
+            }
+            final Connector connector = _client._connector;
+            if (connector != null)
+                connector.startConnection(this);
+        }
+        catch (Exception e)
+        {
+            LOG.debug(e);
+            onConnectionFailed(e);
+        }
+    }
+
+    public void onConnectionFailed(Throwable throwable)
+    {
+        Throwable connect_failure = null;
+
+        boolean startConnection = false;
+        synchronized (this)
+        {
+            _pendingConnections--;
+            if (_pendingReservedConnections > 0)
+            {
+                connect_failure = throwable;
+                _pendingReservedConnections--;
+            }
+            else if (_exchanges.size() > 0)
+            {
+                HttpExchange ex = _exchanges.remove(0);
+                if (ex.setStatus(HttpExchange.STATUS_EXCEPTED))
+                    ex.getEventListener().onConnectionFailed(throwable);
+
+                // Since an existing connection had failed, we need to create a
+                // connection if the  queue is not empty and client is running.
+                if (!_exchanges.isEmpty() && _client.isStarted())
+                    startConnection = true;
+            }
+        }
+
+        if (startConnection)
+            startNewConnection();
+
+        if (connect_failure != null)
+        {
+            try
+            {
+                _reservedConnections.put(connect_failure);
+            }
+            catch (InterruptedException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+    public void onException(Throwable throwable)
+    {
+        synchronized (this)
+        {
+            _pendingConnections--;
+            if (_exchanges.size() > 0)
+            {
+                HttpExchange ex = _exchanges.remove(0);
+                if (ex.setStatus(HttpExchange.STATUS_EXCEPTED))
+                    ex.getEventListener().onException(throwable);
+            }
+        }
+    }
+
+    public void onNewConnection(final AbstractHttpConnection connection) throws IOException
+    {
+        Connection reservedConnection = null;
+
+        synchronized (this)
+        {
+            _pendingConnections--;
+            _connections.add(connection);
+
+            if (_pendingReservedConnections > 0)
+            {
+                reservedConnection = connection;
+                _pendingReservedConnections--;
+            }
+            else
+            {
+                // Establish the tunnel if needed
+                EndPoint endPoint = connection.getEndPoint();
+                if (isProxied() && endPoint instanceof SelectConnector.UpgradableEndPoint)
+                {
+                    SelectConnector.UpgradableEndPoint proxyEndPoint = (SelectConnector.UpgradableEndPoint)endPoint;
+                    ConnectExchange connect = new ConnectExchange(getAddress(), proxyEndPoint);
+                    connect.setAddress(getProxy());
+                    LOG.debug("Establishing tunnel to {} via {}", getAddress(), getProxy());
+                    send(connection, connect);
+                }
+                else
+                {
+                    // Another connection stole the exchange that caused the creation of this connection ?
+                    if (_exchanges.size() == 0)
+                    {
+                        LOG.debug("No exchanges for new connection {}", connection);
+                        connection.setIdleTimeout();
+                        _idleConnections.add(connection);
+                    }
+                    else
+                    {
+                        HttpExchange exchange = _exchanges.remove(0);
+                        send(connection, exchange);
+                    }
+                }
+            }
+        }
+
+        if (reservedConnection != null)
+        {
+            try
+            {
+                _reservedConnections.put(reservedConnection);
+            }
+            catch (InterruptedException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+    public void returnConnection(AbstractHttpConnection connection, boolean close) throws IOException
+    {
+        if (connection.isReserved())
+            connection.setReserved(false);
+
+        if (close)
+        {
+            try
+            {
+                connection.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+        if (!_client.isStarted())
+            return;
+
+        if (!close && connection.getEndPoint().isOpen())
+        {
+            synchronized (this)
+            {
+                if (_exchanges.size() == 0)
+                {
+                    connection.setIdleTimeout();
+                    _idleConnections.add(connection);
+                }
+                else
+                {
+                    HttpExchange ex = _exchanges.remove(0);
+                    send(connection, ex);
+                }
+                this.notifyAll();
+            }
+        }
+        else
+        {
+            boolean startConnection = false;
+            synchronized (this)
+            {
+                _connections.remove(connection);
+                if (!_exchanges.isEmpty())
+                    startConnection = true;
+            }
+
+            if (startConnection)
+                startNewConnection();
+        }
+    }
+
+    public void returnIdleConnection(AbstractHttpConnection connection)
+    {
+        // TODO work out the real idle time;
+        long idleForMs = connection.getEndPoint() != null ? connection.getEndPoint().getMaxIdleTime() : -1;
+        connection.onIdleExpired(idleForMs);
+
+        boolean startConnection = false;
+        synchronized (this)
+        {
+            _idleConnections.remove(connection);
+            _connections.remove(connection);
+
+            if (!_exchanges.isEmpty() && _client.isStarted())
+                startConnection = true;
+        }
+
+        if (startConnection)
+            startNewConnection();
+    }
+
+    public void send(HttpExchange ex) throws IOException
+    {
+        ex.setStatus(HttpExchange.STATUS_WAITING_FOR_CONNECTION);
+
+        LinkedList<String> listeners = _client.getRegisteredListeners();
+        if (listeners != null)
+        {
+            // Add registered listeners, fail if we can't load them
+            for (int i = listeners.size(); i > 0; --i)
+            {
+                String listenerClass = listeners.get(i - 1);
+                try
+                {
+                    Class<?> listener = Class.forName(listenerClass);
+                    Constructor constructor = listener.getDeclaredConstructor(HttpDestination.class, HttpExchange.class);
+                    HttpEventListener elistener = (HttpEventListener)constructor.newInstance(this, ex);
+                    ex.setEventListener(elistener);
+                }
+                catch (final Exception e)
+                {
+                    throw new IOException("Unable to instantiate registered listener for destination: " + listenerClass)
+                    {
+                        {
+                            initCause(e);
+                        }
+                    };
+                }
+            }
+        }
+
+        // Security is supported by default and should be the first consulted
+        if (_client.hasRealms())
+        {
+            ex.setEventListener(new SecurityListener(this, ex));
+        }
+
+        doSend(ex);
+    }
+
+    public void resend(HttpExchange ex) throws IOException
+    {
+        ex.getEventListener().onRetry();
+        ex.reset();
+        doSend(ex);
+    }
+
+    protected void doSend(HttpExchange ex) throws IOException
+    {
+        // add cookies
+        // TODO handle max-age etc.
+        if (_cookies != null)
+        {
+            StringBuilder buf = null;
+            for (HttpCookie cookie : _cookies)
+            {
+                if (buf == null)
+                    buf = new StringBuilder();
+                else
+                    buf.append("; ");
+                buf.append(cookie.getName()); // TODO quotes
+                buf.append("=");
+                buf.append(cookie.getValue()); // TODO quotes
+            }
+            if (buf != null)
+                ex.addRequestHeader(HttpHeaders.COOKIE, buf.toString());
+        }
+
+        // Add any known authorizations
+        if (_authorizations != null)
+        {
+            Authentication auth = (Authentication)_authorizations.match(ex.getRequestURI());
+            if (auth != null)
+                (auth).setCredentials(ex);
+        }
+
+        // Schedule the timeout here, before we queue the exchange
+        // so that we count also the queue time in the timeout
+        ex.scheduleTimeout(this);
+
+        AbstractHttpConnection connection = getIdleConnection();
+        if (connection != null)
+        {
+            send(connection, ex);
+        }
+        else
+        {
+            boolean startConnection = false;
+            synchronized (this)
+            {
+                if (_exchanges.size() == _maxQueueSize)
+                    throw new RejectedExecutionException("Queue full for address " + _address);
+
+                _exchanges.add(ex);
+                if (_connections.size() + _pendingConnections < _maxConnections)
+                    startConnection = true;
+            }
+
+            if (startConnection)
+                startNewConnection();
+        }
+    }
+
+    protected void exchangeExpired(HttpExchange exchange)
+    {
+        // The exchange may expire while waiting in the
+        // destination queue, make sure it is removed
+        synchronized (this)
+        {
+            _exchanges.remove(exchange);
+        }
+    }
+
+    protected void send(AbstractHttpConnection connection, HttpExchange exchange) throws IOException
+    {
+        synchronized (this)
+        {
+            // If server closes the connection, put the exchange back
+            // to the exchange queue and recycle the connection
+            if (!connection.send(exchange))
+            {
+                if (exchange.getStatus() <= HttpExchange.STATUS_WAITING_FOR_CONNECTION)
+                    _exchanges.add(0, exchange);
+                returnIdleConnection(connection);
+            }
+        }
+    }
+
+    @Override
+    public synchronized String toString()
+    {
+        return String.format("HttpDestination@%x//%s:%d(%d/%d,%d,%d/%d)%n", hashCode(), _address.getHost(), _address.getPort(), _connections.size(), _maxConnections, _idleConnections.size(), _exchanges.size(), _maxQueueSize);
+    }
+
+    public synchronized String toDetailString()
+    {
+        StringBuilder b = new StringBuilder();
+        b.append(toString());
+        b.append('\n');
+        synchronized (this)
+        {
+            for (AbstractHttpConnection connection : _connections)
+            {
+                b.append(connection.toDetailString());
+                if (_idleConnections.contains(connection))
+                    b.append(" IDLE");
+                b.append('\n');
+            }
+        }
+        b.append("--");
+        b.append('\n');
+
+        return b.toString();
+    }
+
+    public void setProxy(Address proxy)
+    {
+        _proxy = proxy;
+    }
+
+    public Address getProxy()
+    {
+        return _proxy;
+    }
+
+    public Authentication getProxyAuthentication()
+    {
+        return _proxyAuthentication;
+    }
+
+    public void setProxyAuthentication(Authentication authentication)
+    {
+        _proxyAuthentication = authentication;
+    }
+
+    public boolean isProxied()
+    {
+        return _proxy != null;
+    }
+
+    public void close() throws IOException
+    {
+        synchronized (this)
+        {
+            for (AbstractHttpConnection connection : _connections)
+            {
+                connection.close();
+            }
+        }
+    }
+
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }
+
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        synchronized (this)
+        {
+            out.append(String.valueOf(this));
+            out.append("idle=");
+            out.append(String.valueOf(_idleConnections.size()));
+            out.append(" pending=");
+            out.append(String.valueOf(_pendingConnections));
+            out.append("\n");
+            AggregateLifeCycle.dump(out, indent, _connections);
+        }
+    }
+
+    private class ConnectExchange extends ContentExchange
+    {
+        private final SelectConnector.UpgradableEndPoint proxyEndPoint;
+
+        public ConnectExchange(Address serverAddress, SelectConnector.UpgradableEndPoint proxyEndPoint)
+        {
+            this.proxyEndPoint = proxyEndPoint;
+            setMethod(HttpMethods.CONNECT);
+            String serverHostAndPort = serverAddress.toString();
+            setRequestURI(serverHostAndPort);
+            addRequestHeader(HttpHeaders.HOST, serverHostAndPort);
+            addRequestHeader(HttpHeaders.PROXY_CONNECTION, "keep-alive");
+            addRequestHeader(HttpHeaders.USER_AGENT, "Jetty-Client");
+        }
+
+        @Override
+        protected void onResponseComplete() throws IOException
+        {
+            int responseStatus = getResponseStatus();
+            if (responseStatus == HttpStatus.OK_200)
+            {
+                proxyEndPoint.upgrade();
+            }
+            else if (responseStatus == HttpStatus.GATEWAY_TIMEOUT_504)
+            {
+                onExpire();
+            }
+            else
+            {
+                onException(new ProtocolException("Proxy: " + proxyEndPoint.getRemoteAddr() + ":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus));
+            }
+        }
+
+        @Override
+        protected void onConnectionFailed(Throwable x)
+        {
+            HttpDestination.this.onConnectionFailed(x);
+        }
+
+        @Override
+        protected void onException(Throwable x)
+        {
+            HttpExchange exchange = null;
+            synchronized (HttpDestination.this)
+            {
+                if (!_exchanges.isEmpty())
+                    exchange = _exchanges.remove(0);
+            }
+            if (exchange != null && exchange.setStatus(STATUS_EXCEPTED))
+                exchange.getEventListener().onException(x);
+        }
+
+        @Override
+        protected void onExpire()
+        {
+            HttpExchange exchange = null;
+            synchronized (HttpDestination.this)
+            {
+                if (!_exchanges.isEmpty())
+                    exchange = _exchanges.remove(0);
+            }
+            if (exchange != null && exchange.setStatus(STATUS_EXPIRED))
+                exchange.getEventListener().onExpire();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/HttpEventListener.java b/src/java/org/eclipse/jetty/client/HttpEventListener.java
new file mode 100644
index 0000000..c38b048
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/HttpEventListener.java
@@ -0,0 +1,68 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+
+/**
+ * 
+ * 
+ * 
+ */
+public interface HttpEventListener
+{
+
+    // TODO review the methods here, we can probably trim these down on what to expose
+    
+    public void onRequestCommitted() throws IOException;
+
+
+    public void onRequestComplete() throws IOException;
+
+
+    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException;
+
+
+    public void onResponseHeader(Buffer name, Buffer value) throws IOException;
+
+    
+    public void onResponseHeaderComplete() throws IOException;
+
+    
+    public void onResponseContent(Buffer content) throws IOException;
+
+
+    public void onResponseComplete() throws IOException;
+
+
+    public void onConnectionFailed(Throwable ex);
+
+
+    public void onException(Throwable ex);
+
+
+    public void onExpire();
+    
+    public void onRetry();
+    
+
+}
diff --git a/src/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java b/src/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java
new file mode 100644
index 0000000..152444e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/HttpEventListenerWrapper.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+
+public class HttpEventListenerWrapper implements HttpEventListener
+{
+    HttpEventListener _listener;
+    boolean _delegatingRequests;
+    boolean _delegatingResponses;
+    boolean _delegationResult = true;
+    private Buffer _version;
+    private int _status;
+    private Buffer _reason;
+
+    public HttpEventListenerWrapper()
+    {
+        _listener=null;
+        _delegatingRequests=false;
+        _delegatingResponses=false;
+    }
+    
+    public HttpEventListenerWrapper(HttpEventListener eventListener,boolean delegating)
+    {
+        _listener=eventListener;
+        _delegatingRequests=delegating;
+        _delegatingResponses=delegating;
+    }
+    
+    public HttpEventListener getEventListener()
+    {
+        return _listener;
+    }
+
+    public void setEventListener(HttpEventListener listener)
+    {
+        _listener = listener;
+    }
+
+    public boolean isDelegatingRequests()
+    {
+        return _delegatingRequests;
+    }
+    
+    public boolean isDelegatingResponses()
+    {
+        return _delegatingResponses;
+    }
+
+    public void setDelegatingRequests(boolean delegating)
+    {
+        _delegatingRequests = delegating;
+    }
+    
+    public void setDelegatingResponses(boolean delegating)
+    {
+        _delegatingResponses = delegating;
+    }
+    
+    public void setDelegationResult( boolean result )
+    {
+        _delegationResult = result;
+    }
+    
+    public void onConnectionFailed(Throwable ex)
+    {
+        if (_delegatingRequests)
+            _listener.onConnectionFailed(ex);
+    }
+
+    public void onException(Throwable ex)
+    {
+        if (_delegatingRequests||_delegatingResponses)
+            _listener.onException(ex);
+    }
+
+    public void onExpire()
+    {
+        if (_delegatingRequests||_delegatingResponses)
+            _listener.onExpire();
+    }
+
+    public void onRequestCommitted() throws IOException
+    {
+        if (_delegatingRequests)
+            _listener.onRequestCommitted();
+    }
+
+    public void onRequestComplete() throws IOException
+    {
+        if (_delegatingRequests)
+            _listener.onRequestComplete();
+    }
+
+    public void onResponseComplete() throws IOException
+    {
+        if (_delegatingResponses)
+        {
+            if (_delegationResult == false)
+            {
+                _listener.onResponseStatus(_version,_status,_reason);
+            }
+            _listener.onResponseComplete();
+        }
+    }
+
+    public void onResponseContent(Buffer content) throws IOException
+    {
+        if (_delegatingResponses)
+            _listener.onResponseContent(content);
+    }
+
+    public void onResponseHeader(Buffer name, Buffer value) throws IOException
+    {
+        if (_delegatingResponses)
+            _listener.onResponseHeader(name,value);
+    }
+
+    public void onResponseHeaderComplete() throws IOException
+    {
+        if (_delegatingResponses)
+            _listener.onResponseHeaderComplete();
+    }
+
+    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        if (_delegatingResponses)
+        {
+            _listener.onResponseStatus(version,status,reason);
+        }
+        else
+        {
+            _version = version;
+            _status = status;
+            _reason = reason;
+        }
+    }
+
+    public void onRetry()
+    {
+        if (_delegatingRequests)
+            _listener.onRetry();
+    }
+    
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/client/HttpExchange.java b/src/java/org/eclipse/jetty/client/HttpExchange.java
new file mode 100644
index 0000000..b97b9ff
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/HttpExchange.java
@@ -0,0 +1,1228 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.client.security.SecurityListener;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/**
+ * <p>
+ * An HTTP client API that encapsulates an exchange (a request and its response) with a HTTP server.
+ * </p>
+ *
+ * This object encapsulates:
+ * <ul>
+ * <li>The HTTP server address, see {@link #setAddress(Address)}, or {@link #setURI(URI)}, or {@link #setURL(String)})
+ * <li>The HTTP request method, URI and HTTP version (see {@link #setMethod(String)}, {@link #setRequestURI(String)}, and {@link #setVersion(int)})
+ * <li>The request headers (see {@link #addRequestHeader(String, String)} or {@link #setRequestHeader(String, String)})
+ * <li>The request content (see {@link #setRequestContent(Buffer)} or {@link #setRequestContentSource(InputStream)})
+ * <li>The status of the exchange (see {@link #getStatus()})
+ * <li>Callbacks to handle state changes (see the onXxx methods such as {@link #onRequestComplete()} or {@link #onResponseComplete()})
+ * <li>The ability to intercept callbacks (see {@link #setEventListener(HttpEventListener)}
+ * </ul>
+ *
+ * <p>
+ * The HttpExchange class is intended to be used by a developer wishing to have close asynchronous interaction with the the exchange.<br />
+ * Typically a developer will extend the HttpExchange class with a derived class that overrides some or all of the onXxx callbacks. <br />
+ * There are also some predefined HttpExchange subtypes that can be used as a basis, see {@link org.eclipse.jetty.client.ContentExchange} and
+ * {@link org.eclipse.jetty.client.CachedExchange}.
+ * </p>
+ *
+ * <p>
+ * Typically the HttpExchange is passed to the {@link HttpClient#send(HttpExchange)} method, which in turn selects a {@link HttpDestination} and calls its
+ * {@link HttpDestination#send(HttpExchange)}, which then creates or selects a {@link AbstractHttpConnection} and calls its {@link AbstractHttpConnection#send(HttpExchange)}. A
+ * developer may wish to directly call send on the destination or connection if they wish to bypass some handling provided (eg Cookie handling in the
+ * HttpDestination).
+ * </p>
+ *
+ * <p>
+ * In some circumstances, the HttpClient or HttpDestination may wish to retry a HttpExchange (eg. failed pipeline request, authentication retry or redirection).
+ * In such cases, the HttpClient and/or HttpDestination may insert their own HttpExchangeListener to intercept and filter the call backs intended for the
+ * HttpExchange.
+ * </p>
+ */
+public class HttpExchange
+{
+    static final Logger LOG = Log.getLogger(HttpExchange.class);
+
+    public static final int STATUS_START = 0;
+    public static final int STATUS_WAITING_FOR_CONNECTION = 1;
+    public static final int STATUS_WAITING_FOR_COMMIT = 2;
+    public static final int STATUS_SENDING_REQUEST = 3;
+    public static final int STATUS_WAITING_FOR_RESPONSE = 4;
+    public static final int STATUS_PARSING_HEADERS = 5;
+    public static final int STATUS_PARSING_CONTENT = 6;
+    public static final int STATUS_COMPLETED = 7;
+    public static final int STATUS_EXPIRED = 8;
+    public static final int STATUS_EXCEPTED = 9;
+    public static final int STATUS_CANCELLING = 10;
+    public static final int STATUS_CANCELLED = 11;
+
+    // HTTP protocol fields
+    private String _method = HttpMethods.GET;
+    private Buffer _scheme = HttpSchemes.HTTP_BUFFER;
+    private String _uri;
+    private int _version = HttpVersions.HTTP_1_1_ORDINAL;
+    private Address _address;
+    private final HttpFields _requestFields = new HttpFields();
+    private Buffer _requestContent;
+    private InputStream _requestContentSource;
+
+    private AtomicInteger _status = new AtomicInteger(STATUS_START);
+    private boolean _retryStatus = false;
+    // controls if the exchange will have listeners autoconfigured by the destination
+    private boolean _configureListeners = true;
+    private HttpEventListener _listener = new Listener();
+    private volatile AbstractHttpConnection _connection;
+
+    private Address _localAddress = null;
+
+    // a timeout for this exchange
+    private long _timeout = -1;
+    private volatile Timeout.Task _timeoutTask;
+    private long _lastStateChange=System.currentTimeMillis();
+    private long _sent=-1;
+    private int _lastState=-1;
+    private int _lastStatePeriod=-1;
+
+    boolean _onRequestCompleteDone;
+    boolean _onResponseCompleteDone;
+    boolean _onDone; // == onConnectionFail || onException || onExpired || onCancelled || onResponseCompleted && onRequestCompleted
+
+    protected void expire(HttpDestination destination)
+    {
+        AbstractHttpConnection connection = _connection;
+        if (getStatus() < HttpExchange.STATUS_COMPLETED)
+            setStatus(HttpExchange.STATUS_EXPIRED);
+        destination.exchangeExpired(this);
+        if (connection != null)
+            connection.exchangeExpired(this);
+    }
+
+    public int getStatus()
+    {
+        return _status.get();
+    }
+
+    /**
+     * @param status
+     *            the status to wait for
+     * @throws InterruptedException
+     *             if the waiting thread is interrupted
+     * @deprecated Use {@link #waitForDone()} instead
+     */
+    @Deprecated
+    public void waitForStatus(int status) throws InterruptedException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Wait until the exchange is "done". Done is defined as when a final state has been passed to the HttpExchange via the associated onXxx call. Note that an
+     * exchange can transit a final state when being used as part of a dialog (eg {@link SecurityListener}. Done status is thus defined as:
+     *
+     * <pre>
+     * done == onConnectionFailed || onException || onExpire || onRequestComplete &amp;&amp; onResponseComplete
+     * </pre>
+     *
+     * @return the done status
+     * @throws InterruptedException
+     */
+    public int waitForDone() throws InterruptedException
+    {
+        synchronized (this)
+        {
+            while (!isDone())
+                this.wait();
+            return _status.get();
+        }
+    }
+
+    public void reset()
+    {
+        // TODO - this should do a cancel and wakeup everybody that was waiting.
+        // might need a version number concept
+        synchronized (this)
+        {
+            _timeoutTask = null;
+            _onRequestCompleteDone = false;
+            _onResponseCompleteDone = false;
+            _onDone = false;
+            setStatus(STATUS_START);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param newStatus
+     * @return True if the status was actually set.
+     */
+    boolean setStatus(int newStatus)
+    {
+        boolean set = false;
+        try
+        {
+            int oldStatus = _status.get();
+            boolean ignored = false;
+            if (oldStatus != newStatus)
+            {
+                long now = System.currentTimeMillis();
+                _lastStatePeriod=(int)(now-_lastStateChange);
+                _lastState=oldStatus;
+                _lastStateChange=now;
+                if (newStatus==STATUS_SENDING_REQUEST)
+                    _sent=_lastStateChange;
+            }
+            
+            // State machine: from which old status you can go into which new status
+            switch (oldStatus)
+            {
+                case STATUS_START:
+                    switch (newStatus)
+                    {
+                        case STATUS_START:
+                        case STATUS_WAITING_FOR_CONNECTION:
+                        case STATUS_WAITING_FOR_COMMIT:
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_WAITING_FOR_CONNECTION:
+                    switch (newStatus)
+                    {
+                        case STATUS_WAITING_FOR_COMMIT:
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_WAITING_FOR_COMMIT:
+                    switch (newStatus)
+                    {
+                        case STATUS_SENDING_REQUEST:
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_SENDING_REQUEST:
+                    switch (newStatus)
+                    {
+                        case STATUS_WAITING_FOR_RESPONSE:
+                            if (set = _status.compareAndSet(oldStatus,newStatus))
+                                getEventListener().onRequestCommitted();
+                            break;
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_WAITING_FOR_RESPONSE:
+                    switch (newStatus)
+                    {
+                        case STATUS_PARSING_HEADERS:
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_PARSING_HEADERS:
+                    switch (newStatus)
+                    {
+                        case STATUS_PARSING_CONTENT:
+                            if (set = _status.compareAndSet(oldStatus,newStatus))
+                                getEventListener().onResponseHeaderComplete();
+                            break;
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_PARSING_CONTENT:
+                    switch (newStatus)
+                    {
+                        case STATUS_COMPLETED:
+                            if (set = _status.compareAndSet(oldStatus,newStatus))
+                                getEventListener().onResponseComplete();
+                            break;
+                        case STATUS_CANCELLING:
+                        case STATUS_EXCEPTED:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_EXPIRED:
+                            set = setStatusExpired(newStatus,oldStatus);
+                            break;
+                    }
+                    break;
+                case STATUS_COMPLETED:
+                    switch (newStatus)
+                    {
+                        case STATUS_START:
+                        case STATUS_EXCEPTED:
+                        case STATUS_WAITING_FOR_RESPONSE:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                        case STATUS_CANCELLING:
+                        case STATUS_EXPIRED:
+                            // Don't change the status, it's too late
+                            ignored = true;
+                            break;
+                    }
+                    break;
+                case STATUS_CANCELLING:
+                    switch (newStatus)
+                    {
+                        case STATUS_EXCEPTED:
+                        case STATUS_CANCELLED:
+                            if (set = _status.compareAndSet(oldStatus,newStatus))
+                                done();
+                            break;
+                        default:
+                            // Ignore other statuses, we're cancelling
+                            ignored = true;
+                            break;
+                    }
+                    break;
+                case STATUS_EXCEPTED:
+                case STATUS_EXPIRED:
+                case STATUS_CANCELLED:
+                    switch (newStatus)
+                    {
+                        case STATUS_START:
+                            set = _status.compareAndSet(oldStatus,newStatus);
+                            break;
+                            
+                        case STATUS_COMPLETED:
+                            ignored = true;
+                            done();
+                            break; 
+                            
+                        default:
+                            ignored = true;
+                            break;
+                    }
+                    break;
+                default:
+                    // Here means I allowed to set a state that I don't recognize
+                    throw new AssertionError(oldStatus + " => " + newStatus);
+            }
+
+            if (!set && !ignored)
+                throw new IllegalStateException(toState(oldStatus) + " => " + toState(newStatus));
+            LOG.debug("setStatus {} {}",newStatus,this);
+        }
+        catch (IOException x)
+        {
+            LOG.warn(x);
+        }
+        return set;
+    }
+
+    private boolean setStatusExpired(int newStatus, int oldStatus)
+    {
+        boolean set;
+        if (set = _status.compareAndSet(oldStatus,newStatus))
+            getEventListener().onExpire();
+        return set;
+    }
+
+    public boolean isDone()
+    {
+        synchronized (this)
+        {
+            return _onDone;
+        }
+    }
+
+    /**
+     * @deprecated
+     */
+    @Deprecated
+    public boolean isDone(int status)
+    {
+        return isDone();
+    }
+
+    public HttpEventListener getEventListener()
+    {
+        return _listener;
+    }
+
+    public void setEventListener(HttpEventListener listener)
+    {
+        _listener = listener;
+    }
+
+    public void setTimeout(long timeout)
+    {
+        _timeout = timeout;
+    }
+
+    public long getTimeout()
+    {
+        return _timeout;
+    }
+
+    /**
+     * @param url
+     *            an absolute URL (for example 'http://localhost/foo/bar?a=1')
+     */
+    public void setURL(String url)
+    {
+        setURI(URI.create(url));
+    }
+
+    /**
+     * @param address
+     *            the address of the server
+     */
+    public void setAddress(Address address)
+    {
+        _address = address;
+    }
+
+    /**
+     * @return the address of the server
+     */
+    public Address getAddress()
+    {
+        return _address;
+    }
+
+    /**
+     * the local address used by the connection
+     *
+     * Note: this method will not be populated unless the exchange has been executed by the HttpClient
+     *
+     * @return the local address used for the running of the exchange if available, null otherwise.
+     */
+    public Address getLocalAddress()
+    {
+        return _localAddress;
+    }
+
+    /**
+     * @param scheme
+     *            the scheme of the URL (for example 'http')
+     */
+    public void setScheme(Buffer scheme)
+    {
+        _scheme = scheme;
+    }
+
+    /**
+     * @param scheme
+     *            the scheme of the URL (for example 'http')
+     */
+    public void setScheme(String scheme)
+    {
+        if (scheme != null)
+        {
+            if (HttpSchemes.HTTP.equalsIgnoreCase(scheme))
+                setScheme(HttpSchemes.HTTP_BUFFER);
+            else if (HttpSchemes.HTTPS.equalsIgnoreCase(scheme))
+                setScheme(HttpSchemes.HTTPS_BUFFER);
+            else
+                setScheme(new ByteArrayBuffer(scheme));
+        }
+    }
+
+    /**
+     * @return the scheme of the URL
+     */
+    public Buffer getScheme()
+    {
+        return _scheme;
+    }
+
+    /**
+     * @param version
+     *            the HTTP protocol version as integer, 9, 10 or 11 for 0.9, 1.0 or 1.1
+     */
+    public void setVersion(int version)
+    {
+        _version = version;
+    }
+
+    /**
+     * @param version
+     *            the HTTP protocol version as string
+     */
+    public void setVersion(String version)
+    {
+        CachedBuffer v = HttpVersions.CACHE.get(version);
+        if (v == null)
+            _version = 10;
+        else
+            _version = v.getOrdinal();
+    }
+
+    /**
+     * @return the HTTP protocol version as integer
+     * @see #setVersion(int)
+     */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /**
+     * @param method
+     *            the HTTP method (for example 'GET')
+     */
+    public void setMethod(String method)
+    {
+        _method = method;
+    }
+
+    /**
+     * @return the HTTP method
+     */
+    public String getMethod()
+    {
+        return _method;
+    }
+
+    /**
+     * @return request URI
+     * @see #getRequestURI()
+     * @deprecated
+     */
+    @Deprecated
+    public String getURI()
+    {
+        return getRequestURI();
+    }
+
+    /**
+     * @return request URI
+     */
+    public String getRequestURI()
+    {
+        return _uri;
+    }
+
+    /**
+     * Set the request URI
+     *
+     * @param uri
+     *            new request URI
+     * @see #setRequestURI(String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setURI(String uri)
+    {
+        setRequestURI(uri);
+    }
+
+    /**
+     * Set the request URI
+     *
+     * Per RFC 2616 sec5, Request-URI = "*" | absoluteURI | abs_path | authority<br/>
+     * where:<br/>
+     * <br/>
+     * "*" - request applies to server itself<br/>
+     * absoluteURI - required for proxy requests, e.g. http://localhost:8080/context<br/>
+     * (this form is generated automatically by HttpClient)<br/>
+     * abs_path - used for most methods, e.g. /context<br/>
+     * authority - used for CONNECT method only, e.g. localhost:8080<br/>
+     * <br/>
+     * For complete definition of URI components, see RFC 2396 sec3.<br/>
+     *
+     * @param uri
+     *            new request URI
+     */
+    public void setRequestURI(String uri)
+    {
+        _uri = uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param uri
+     *            an absolute URI (for example 'http://localhost/foo/bar?a=1')
+     */
+    public void setURI(URI uri)
+    {
+        if (!uri.isAbsolute())
+            throw new IllegalArgumentException("!Absolute URI: " + uri);
+
+        if (uri.isOpaque())
+            throw new IllegalArgumentException("Opaque URI: " + uri);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("URI = {}",uri.toASCIIString());
+
+        String scheme = uri.getScheme();
+        int port = uri.getPort();
+        if (port <= 0)
+            port = "https".equalsIgnoreCase(scheme)?443:80;
+
+        setScheme(scheme);
+        setAddress(new Address(uri.getHost(),port));
+
+        HttpURI httpUri = new HttpURI(uri);
+        String completePath = httpUri.getCompletePath();
+        setRequestURI(completePath == null?"/":completePath);
+    }
+
+    /**
+     * Adds the specified request header
+     *
+     * @param name
+     *            the header name
+     * @param value
+     *            the header value
+     */
+    public void addRequestHeader(String name, String value)
+    {
+        getRequestFields().add(name,value);
+    }
+
+    /**
+     * Adds the specified request header
+     *
+     * @param name
+     *            the header name
+     * @param value
+     *            the header value
+     */
+    public void addRequestHeader(Buffer name, Buffer value)
+    {
+        getRequestFields().add(name,value);
+    }
+
+    /**
+     * Sets the specified request header
+     *
+     * @param name
+     *            the header name
+     * @param value
+     *            the header value
+     */
+    public void setRequestHeader(String name, String value)
+    {
+        getRequestFields().put(name,value);
+    }
+
+    /**
+     * Sets the specified request header
+     *
+     * @param name
+     *            the header name
+     * @param value
+     *            the header value
+     */
+    public void setRequestHeader(Buffer name, Buffer value)
+    {
+        getRequestFields().put(name,value);
+    }
+
+    /**
+     * @param value
+     *            the content type of the request
+     */
+    public void setRequestContentType(String value)
+    {
+        getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,value);
+    }
+
+    /**
+     * @return the request headers
+     */
+    public HttpFields getRequestFields()
+    {
+        return _requestFields;
+    }
+
+    /**
+     * @param requestContent
+     *            the request content
+     */
+    public void setRequestContent(Buffer requestContent)
+    {
+        _requestContent = requestContent;
+    }
+
+    /**
+     * @param stream
+     *            the request content as a stream
+     */
+    public void setRequestContentSource(InputStream stream)
+    {
+        _requestContentSource = stream;
+        if (_requestContentSource != null && _requestContentSource.markSupported())
+            _requestContentSource.mark(Integer.MAX_VALUE);
+    }
+
+    /**
+     * @return the request content as a stream
+     */
+    public InputStream getRequestContentSource()
+    {
+        return _requestContentSource;
+    }
+
+    public Buffer getRequestContentChunk(Buffer buffer) throws IOException
+    {
+        synchronized (this)
+        {
+            if (_requestContentSource!=null)
+            {
+                if (buffer == null)
+                    buffer = new ByteArrayBuffer(8192); // TODO configure
+
+                int space = buffer.space();
+                int length = _requestContentSource.read(buffer.array(),buffer.putIndex(),space);
+                if (length >= 0)
+                {
+                    buffer.setPutIndex(buffer.putIndex()+length);
+                    return buffer;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * @return the request content
+     */
+    public Buffer getRequestContent()
+    {
+        return _requestContent;
+    }
+
+    /**
+     * @return whether a retry will be attempted or not
+     */
+    public boolean getRetryStatus()
+    {
+        return _retryStatus;
+    }
+
+    /**
+     * @param retryStatus
+     *            whether a retry will be attempted or not
+     */
+    public void setRetryStatus(boolean retryStatus)
+    {
+        _retryStatus = retryStatus;
+    }
+
+    /**
+     * Initiates the cancelling of this exchange. The status of the exchange is set to {@link #STATUS_CANCELLING}. Cancelling the exchange is an asynchronous
+     * operation with respect to the request/response, and as such checking the request/response status of a cancelled exchange may return undefined results
+     * (for example it may have only some of the response headers being sent by the server). The cancelling of the exchange is completed when the exchange
+     * status (see {@link #getStatus()}) is {@link #STATUS_CANCELLED}, and this can be waited using {@link #waitForDone()}.
+     */
+    public void cancel()
+    {
+        setStatus(STATUS_CANCELLING);
+        abort();
+    }
+
+    private void done()
+    {
+        synchronized (this)
+        {
+            disassociate();
+            _onDone = true;
+            notifyAll();
+        }
+    }
+
+    private void abort()
+    {
+        AbstractHttpConnection httpConnection = _connection;
+        if (httpConnection != null)
+        {
+            try
+            {
+                // Closing the connection here will cause the connection
+                // to be returned in HttpConnection.handle()
+                httpConnection.close();
+            }
+            catch (IOException x)
+            {
+                LOG.debug(x);
+            }
+            finally
+            {
+                disassociate();
+            }
+        }
+    }
+
+    void associate(AbstractHttpConnection connection)
+    {
+        if (connection.getEndPoint().getLocalAddr() != null)
+            _localAddress = new Address(connection.getEndPoint().getLocalAddr(),connection.getEndPoint().getLocalPort());
+
+        _connection = connection;
+        if (getStatus() == STATUS_CANCELLING)
+            abort();
+    }
+
+    boolean isAssociated()
+    {
+        return this._connection != null;
+    }
+
+    AbstractHttpConnection disassociate()
+    {
+        AbstractHttpConnection result = _connection;
+        this._connection = null;
+        if (getStatus() == STATUS_CANCELLING)
+            setStatus(STATUS_CANCELLED);
+        return result;
+    }
+
+    public static String toState(int s)
+    {
+        String state;
+        switch (s)
+        {
+            case STATUS_START:
+                state = "START";
+                break;
+            case STATUS_WAITING_FOR_CONNECTION:
+                state = "CONNECTING";
+                break;
+            case STATUS_WAITING_FOR_COMMIT:
+                state = "CONNECTED";
+                break;
+            case STATUS_SENDING_REQUEST:
+                state = "SENDING";
+                break;
+            case STATUS_WAITING_FOR_RESPONSE:
+                state = "WAITING";
+                break;
+            case STATUS_PARSING_HEADERS:
+                state = "HEADERS";
+                break;
+            case STATUS_PARSING_CONTENT:
+                state = "CONTENT";
+                break;
+            case STATUS_COMPLETED:
+                state = "COMPLETED";
+                break;
+            case STATUS_EXPIRED:
+                state = "EXPIRED";
+                break;
+            case STATUS_EXCEPTED:
+                state = "EXCEPTED";
+                break;
+            case STATUS_CANCELLING:
+                state = "CANCELLING";
+                break;
+            case STATUS_CANCELLED:
+                state = "CANCELLED";
+                break;
+            default:
+                state = "UNKNOWN";
+        }
+        return state;
+    }
+
+    @Override
+    public String toString()
+    {
+        String state=toState(getStatus());
+        long now=System.currentTimeMillis();
+        long forMs = now -_lastStateChange;
+        String s= _lastState>=0
+            ?String.format("%s@%x=%s//%s%s#%s(%dms)->%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,toState(_lastState),_lastStatePeriod,state,forMs)
+            :String.format("%s@%x=%s//%s%s#%s(%dms)",getClass().getSimpleName(),hashCode(),_method,_address,_uri,state,forMs);
+        if (getStatus()>=STATUS_SENDING_REQUEST && _sent>0)
+            s+="sent="+(now-_sent)+"ms";
+        return s;
+    }
+
+    /**
+     */
+    protected Connection onSwitchProtocol(EndPoint endp) throws IOException
+    {
+        return null;
+    }
+
+    /**
+     * Callback called when the request headers have been sent to the server. This implementation does nothing.
+     *
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onRequestCommitted() throws IOException
+    {
+    }
+
+    /**
+     * Callback called when the request and its body have been sent to the server. This implementation does nothing.
+     *
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onRequestComplete() throws IOException
+    {
+    }
+
+    /**
+     * Callback called when a response status line has been received from the server. This implementation does nothing.
+     *
+     * @param version
+     *            the HTTP version
+     * @param status
+     *            the HTTP status code
+     * @param reason
+     *            the HTTP status reason string
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+    }
+
+    /**
+     * Callback called for each response header received from the server. This implementation does nothing.
+     *
+     * @param name
+     *            the header name
+     * @param value
+     *            the header value
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
+    {
+    }
+
+    /**
+     * Callback called when the response headers have been completely received from the server. This implementation does nothing.
+     *
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onResponseHeaderComplete() throws IOException
+    {
+    }
+
+    /**
+     * Callback called for each chunk of the response content received from the server. This implementation does nothing.
+     *
+     * @param content
+     *            the buffer holding the content chunk
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onResponseContent(Buffer content) throws IOException
+    {
+    }
+
+    /**
+     * Callback called when the entire response has been received from the server This implementation does nothing.
+     *
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onResponseComplete() throws IOException
+    {
+    }
+
+    /**
+     * Callback called when an exception was thrown during an attempt to establish the connection with the server (for example the server is not listening).
+     * This implementation logs a warning.
+     *
+     * @param x
+     *            the exception thrown attempting to establish the connection with the server
+     */
+    protected void onConnectionFailed(Throwable x)
+    {
+        LOG.warn("CONNECTION FAILED " + this,x);
+    }
+
+    /**
+     * Callback called when any other exception occurs during the handling of this exchange. This implementation logs a warning.
+     *
+     * @param x
+     *            the exception thrown during the handling of this exchange
+     */
+    protected void onException(Throwable x)
+    {
+        LOG.warn("EXCEPTION " + this,x);
+    }
+
+    /**
+     * Callback called when no response has been received within the timeout. This implementation logs a warning.
+     */
+    protected void onExpire()
+    {
+        LOG.warn("EXPIRED " + this);
+    }
+
+    /**
+     * Callback called when the request is retried (due to failures or authentication). Implementations must reset any consumable content that needs to be sent.
+     *
+     * @throws IOException
+     *             allowed to be thrown by overriding code
+     */
+    protected void onRetry() throws IOException
+    {
+        if (_requestContentSource != null)
+        {
+            if (_requestContentSource.markSupported())
+            {
+                _requestContent = null;
+                _requestContentSource.reset();
+            }
+            else
+            {
+                throw new IOException("Unsupported retry attempt");
+            }
+        }
+    }
+
+    /**
+     * @return true if the exchange should have listeners configured for it by the destination, false if this is being managed elsewhere
+     * @see #setConfigureListeners(boolean)
+     */
+    public boolean configureListeners()
+    {
+        return _configureListeners;
+    }
+
+    /**
+     * @param autoConfigure
+     *            whether the listeners are configured by the destination or elsewhere
+     */
+    public void setConfigureListeners(boolean autoConfigure)
+    {
+        this._configureListeners = autoConfigure;
+    }
+
+    protected void scheduleTimeout(final HttpDestination destination)
+    {
+        assert _timeoutTask == null;
+
+        _timeoutTask = new Timeout.Task()
+        {
+            @Override
+            public void expired()
+            {
+                HttpExchange.this.expire(destination);
+            }
+        };
+
+        HttpClient httpClient = destination.getHttpClient();
+        long timeout = getTimeout();
+        if (timeout > 0)
+            httpClient.schedule(_timeoutTask,timeout);
+        else
+            httpClient.schedule(_timeoutTask);
+    }
+
+    protected void cancelTimeout(HttpClient httpClient)
+    {
+        Timeout.Task task = _timeoutTask;
+        if (task != null)
+            httpClient.cancel(task);
+        _timeoutTask = null;
+    }
+
+    private class Listener implements HttpEventListener
+    {
+        public void onConnectionFailed(Throwable ex)
+        {
+            try
+            {
+                HttpExchange.this.onConnectionFailed(ex);
+            }
+            finally
+            {
+                done();
+            }
+        }
+
+        public void onException(Throwable ex)
+        {
+            try
+            {
+                HttpExchange.this.onException(ex);
+            }
+            finally
+            {
+                done();
+            }
+        }
+
+        public void onExpire()
+        {
+            try
+            {
+                HttpExchange.this.onExpire();
+            }
+            finally
+            {
+                done();
+            }
+        }
+
+        public void onRequestCommitted() throws IOException
+        {
+            HttpExchange.this.onRequestCommitted();
+        }
+
+        public void onRequestComplete() throws IOException
+        {
+            try
+            {
+                HttpExchange.this.onRequestComplete();
+            }
+            finally
+            {
+                synchronized (HttpExchange.this)
+                {
+                    _onRequestCompleteDone = true;
+                    // Member _onDone may already be true, for example
+                    // because the exchange expired or has been canceled
+                    _onDone |= _onResponseCompleteDone;
+                    if (_onDone)
+                        disassociate();
+                    HttpExchange.this.notifyAll();
+                }
+            }
+        }
+
+        public void onResponseComplete() throws IOException
+        {
+            try
+            {
+                HttpExchange.this.onResponseComplete();
+            }
+            finally
+            {
+                synchronized (HttpExchange.this)
+                {
+                    _onResponseCompleteDone = true;
+                    // Member _onDone may already be true, for example
+                    // because the exchange expired or has been canceled
+                    _onDone |= _onRequestCompleteDone;
+                    if (_onDone)
+                        disassociate();
+                    HttpExchange.this.notifyAll();
+                }
+            }
+        }
+
+        public void onResponseContent(Buffer content) throws IOException
+        {
+            HttpExchange.this.onResponseContent(content);
+        }
+
+        public void onResponseHeader(Buffer name, Buffer value) throws IOException
+        {
+            HttpExchange.this.onResponseHeader(name,value);
+        }
+
+        public void onResponseHeaderComplete() throws IOException
+        {
+            HttpExchange.this.onResponseHeaderComplete();
+        }
+
+        public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+        {
+            HttpExchange.this.onResponseStatus(version,status,reason);
+        }
+
+        public void onRetry()
+        {
+            HttpExchange.this.setRetryStatus(true);
+            try
+            {
+                HttpExchange.this.onRetry();
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+    }
+
+    /**
+     * @deprecated use {@link org.eclipse.jetty.client.CachedExchange} instead
+     */
+    @Deprecated
+    public static class CachedExchange extends org.eclipse.jetty.client.CachedExchange
+    {
+        public CachedExchange(boolean cacheFields)
+        {
+            super(cacheFields);
+        }
+    }
+
+    /**
+     * @deprecated use {@link org.eclipse.jetty.client.ContentExchange} instead
+     */
+    @Deprecated
+    public static class ContentExchange extends org.eclipse.jetty.client.ContentExchange
+    {
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/RedirectListener.java b/src/java/org/eclipse/jetty/client/RedirectListener.java
new file mode 100644
index 0000000..3336dfb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/RedirectListener.java
@@ -0,0 +1,212 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+
+/**
+ * RedirectListener
+ *
+ * Detect and handle the redirect responses
+ */
+public class RedirectListener extends HttpEventListenerWrapper
+{
+    private final HttpExchange _exchange;
+    private HttpDestination _destination;
+    private String _location;
+    private int _attempts;
+    private boolean _requestComplete;
+    private boolean _responseComplete;
+    private boolean _redirected;
+
+    public RedirectListener(HttpDestination destination, HttpExchange ex)
+    {
+        // Start of sending events through to the wrapped listener
+        // Next decision point is the onResponseStatus
+        super(ex.getEventListener(),true);
+
+        _destination = destination;
+        _exchange = ex;
+    }
+
+    @Override
+    public void onResponseStatus( Buffer version, int status, Buffer reason )
+        throws IOException
+    {
+        _redirected = ((status == HttpStatus.MOVED_PERMANENTLY_301 ||
+                        status == HttpStatus.MOVED_TEMPORARILY_302) &&
+                       _attempts < _destination.getHttpClient().maxRedirects());
+
+        if (_redirected)
+        {
+            setDelegatingRequests(false);
+            setDelegatingResponses(false);
+        }
+
+        super.onResponseStatus(version,status,reason);
+    }
+
+
+    @Override
+    public void onResponseHeader( Buffer name, Buffer value )
+        throws IOException
+    {
+        if (_redirected)
+        {
+            int header = HttpHeaders.CACHE.getOrdinal(name);
+            switch (header)
+            {
+                case HttpHeaders.LOCATION_ORDINAL:
+                    _location = value.toString();
+                    break;
+            }
+        }
+        super.onResponseHeader(name,value);
+    }
+
+    @Override
+    public void onRequestComplete() throws IOException
+    {
+        _requestComplete = true;
+
+        if (checkExchangeComplete())
+        {
+            super.onRequestComplete();
+        }
+    }
+
+    @Override
+    public void onResponseComplete() throws IOException
+    {
+        _responseComplete = true;
+
+        if (checkExchangeComplete())
+        {
+            super.onResponseComplete();
+        }
+    }
+
+    public boolean checkExchangeComplete()
+        throws IOException
+    {
+        if (_redirected && _requestComplete && _responseComplete)
+        {
+            if (_location != null)
+            {
+                if (_location.indexOf("://")>0)
+                {
+                    _exchange.setURL(_location);
+                }
+                else
+                {
+                    _exchange.setRequestURI(_location);
+                }
+
+                // destination may have changed
+                boolean isHttps = HttpSchemes.HTTPS.equals(String.valueOf(_exchange.getScheme()));
+                HttpDestination destination=_destination.getHttpClient().getDestination(_exchange.getAddress(),isHttps);
+
+                if (_destination==destination)
+                {
+                    _destination.resend(_exchange);
+                }
+                else
+                {
+                    // unwrap to find ultimate listener.
+                    HttpEventListener listener=this;
+                    while(listener instanceof HttpEventListenerWrapper)
+                    {
+                        listener=((HttpEventListenerWrapper)listener).getEventListener();
+                    }
+                    
+                    //reset the listener
+                    _exchange.getEventListener().onRetry();
+                    _exchange.reset();
+                    _exchange.setEventListener(listener);
+
+                    // Set the new Host header
+                    Address address = _exchange.getAddress();
+                    int port = address.getPort();
+                    StringBuilder hostHeader = new StringBuilder( 64 );
+                    hostHeader.append( address.getHost() );
+                    if( !( ( port == 80 && !isHttps ) || ( port == 443 && isHttps ) ) ) 
+                    {
+                        hostHeader.append( ':' );
+                        hostHeader.append( port );
+                    }
+                    
+                    _exchange.setRequestHeader( HttpHeaders.HOST, hostHeader.toString() );
+
+                    destination.send(_exchange);
+                }
+
+                return false;
+            }
+            else
+            {
+                setDelegationResult(false);
+            }
+        }
+
+        return true;
+    }
+
+    public void onRetry()
+    {
+        _redirected=false;
+        _attempts++;
+
+        setDelegatingRequests(true);
+        setDelegatingResponses(true);
+
+        _requestComplete=false;
+        _responseComplete=false;
+
+        super.onRetry();
+    }
+
+    /**
+     * Delegate failed connection
+     */
+    @Override
+    public void onConnectionFailed( Throwable ex )
+    {
+        setDelegatingRequests(true);
+        setDelegatingResponses(true);
+
+        super.onConnectionFailed( ex );
+    }
+
+    /**
+     * Delegate onException
+     */
+    @Override
+    public void onException( Throwable ex )
+    {
+        setDelegatingRequests(true);
+        setDelegatingResponses(true);
+
+        super.onException( ex );
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/SelectConnector.java b/src/java/org/eclipse/jetty/client/SelectConnector.java
new file mode 100644
index 0000000..4884221
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/SelectConnector.java
@@ -0,0 +1,449 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+class SelectConnector extends AggregateLifeCycle implements HttpClient.Connector, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(SelectConnector.class);
+
+    private final HttpClient _httpClient;
+    private final Manager _selectorManager=new Manager();
+    private final Map<SocketChannel, Timeout.Task> _connectingChannels = new ConcurrentHashMap<SocketChannel, Timeout.Task>();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param httpClient the HttpClient this connector is associated to. It is 
+     * added via the {@link #addBean(Object, boolean)} as an unmanaged bean.
+     */
+    SelectConnector(HttpClient httpClient)
+    {
+        _httpClient = httpClient;
+        addBean(_httpClient,false);
+        addBean(_selectorManager,true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void startConnection( HttpDestination destination )
+        throws IOException
+    {
+        SocketChannel channel = null;
+        try
+        {
+            channel = SocketChannel.open();
+            Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress();
+            channel.socket().setTcpNoDelay(true);
+
+            if (_httpClient.isConnectBlocking())
+            {
+                channel.socket().connect(address.toSocketAddress(), _httpClient.getConnectTimeout());
+                channel.configureBlocking(false);
+                _selectorManager.register( channel, destination );
+            }
+            else
+            {
+                channel.configureBlocking(false);
+                channel.connect(address.toSocketAddress());
+                _selectorManager.register(channel,destination);
+                ConnectTimeout connectTimeout = new ConnectTimeout(channel,destination);
+                _httpClient.schedule(connectTimeout,_httpClient.getConnectTimeout());
+                _connectingChannels.put(channel,connectTimeout);
+            }
+        }
+        catch (UnresolvedAddressException ex)
+        {
+            if (channel != null)
+                channel.close();
+            destination.onConnectionFailed(ex);
+        }
+        catch(IOException ex)
+        {
+            if (channel != null)
+                channel.close();
+            destination.onConnectionFailed(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    class Manager extends SelectorManager
+    {
+        Logger LOG = SelectConnector.LOG;
+
+        @Override
+        public boolean dispatch(Runnable task)
+        {
+            return _httpClient._threadPool.dispatch(task);
+        }
+
+        @Override
+        protected void endPointOpened(SelectChannelEndPoint endpoint)
+        {
+        }
+
+        @Override
+        protected void endPointClosed(SelectChannelEndPoint endpoint)
+        {
+        }
+
+        @Override
+        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
+        {
+        }
+
+        @Override
+        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
+        {
+            return new AsyncHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
+        {
+            // We're connected, cancel the connect timeout
+            Timeout.Task connectTimeout = _connectingChannels.remove(channel);
+            if (connectTimeout != null)
+                connectTimeout.cancel();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Channels with connection pending: {}", _connectingChannels.size());
+
+            // key should have destination at this point (will be replaced by endpoint after this call)
+            HttpDestination dest=(HttpDestination)key.attachment();
+
+            SelectChannelEndPoint scep = new SelectChannelEndPoint(channel, selectSet, key, (int)_httpClient.getIdleTimeout());
+            AsyncEndPoint ep = scep;
+
+            if (dest.isSecure())
+            {
+                LOG.debug("secure to {}, proxied={}",channel,dest.isProxied());
+                ep = new UpgradableEndPoint(ep,newSslEngine(dest.getSslContextFactory(), channel));
+            }
+
+            AsyncConnection connection = selectSet.getManager().newConnection(channel,ep, key.attachment());
+            ep.setConnection(connection);
+
+            AbstractHttpConnection httpConnection=(AbstractHttpConnection)connection;
+            httpConnection.setDestination(dest);
+
+            if (dest.isSecure() && !dest.isProxied())
+                ((UpgradableEndPoint)ep).upgrade();
+
+            dest.onNewConnection(httpConnection);
+
+            return scep;
+        }
+
+        private synchronized SSLEngine newSslEngine(SslContextFactory sslContextFactory, SocketChannel channel) throws IOException
+        {
+            SSLEngine sslEngine;
+            if (channel != null)
+            {
+                String peerHost = channel.socket().getInetAddress().getHostAddress();
+                int peerPort = channel.socket().getPort();
+                sslEngine = sslContextFactory.newSslEngine(peerHost, peerPort);
+            }
+            else
+            {
+                sslEngine = sslContextFactory.newSslEngine();
+            }
+            sslEngine.setUseClientMode(true);
+            sslEngine.beginHandshake();
+
+            return sslEngine;
+        }
+
+        /* ------------------------------------------------------------ */
+        /* (non-Javadoc)
+         * @see org.eclipse.io.nio.SelectorManager#connectionFailed(java.nio.channels.SocketChannel, java.lang.Throwable, java.lang.Object)
+         */
+        @Override
+        protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+        {
+            Timeout.Task connectTimeout = _connectingChannels.remove(channel);
+            if (connectTimeout != null)
+                connectTimeout.cancel();
+
+            if (attachment instanceof HttpDestination)
+                ((HttpDestination)attachment).onConnectionFailed(ex);
+            else
+                super.connectionFailed(channel,ex,attachment);
+        }
+    }
+
+    private class ConnectTimeout extends Timeout.Task
+    {
+        private final SocketChannel channel;
+        private final HttpDestination destination;
+
+        public ConnectTimeout(SocketChannel channel, HttpDestination destination)
+        {
+            this.channel = channel;
+            this.destination = destination;
+        }
+
+        @Override
+        public void expired()
+        {
+            if (channel.isConnectionPending())
+            {
+                LOG.debug("Channel {} timed out while connecting, closing it", channel);
+                close();
+                _connectingChannels.remove(channel);
+                destination.onConnectionFailed(new SocketTimeoutException());
+            }
+        }
+
+        private void close()
+        {
+            try
+            {
+                // This will unregister the channel from the selector
+                channel.close();
+            }
+            catch (IOException x)
+            {
+                LOG.ignore(x);
+            }
+        }
+    }
+
+    public static class UpgradableEndPoint implements AsyncEndPoint
+    {
+        AsyncEndPoint _endp;
+        SSLEngine _engine;
+
+        public UpgradableEndPoint(AsyncEndPoint endp, SSLEngine engine) throws IOException
+        {
+            _engine=engine;
+            _endp=endp;
+        }
+
+        public void upgrade()
+        {
+            AsyncHttpConnection connection = (AsyncHttpConnection)_endp.getConnection();
+
+            SslConnection sslConnection = new SslConnection(_engine,_endp);
+            _endp.setConnection(sslConnection);
+
+            _endp=sslConnection.getSslEndPoint();
+            sslConnection.getSslEndPoint().setConnection(connection);
+
+            LOG.debug("upgrade {} to {} for {}",this,sslConnection,connection);
+        }
+
+
+        public Connection getConnection()
+        {
+            return _endp.getConnection();
+        }
+
+        public void setConnection(Connection connection)
+        {
+            _endp.setConnection(connection);
+        }
+
+        public void shutdownOutput() throws IOException
+        {
+            _endp.shutdownOutput();
+        }
+
+        public void dispatch()
+        {
+            _endp.asyncDispatch();
+        }
+
+        public void asyncDispatch()
+        {
+            _endp.asyncDispatch();
+        }
+
+        public boolean isOutputShutdown()
+        {
+            return _endp.isOutputShutdown();
+        }
+
+        public void shutdownInput() throws IOException
+        {
+            _endp.shutdownInput();
+        }
+
+        public void scheduleWrite()
+        {
+            _endp.scheduleWrite();
+        }
+
+        public boolean isInputShutdown()
+        {
+            return _endp.isInputShutdown();
+        }
+
+        public void close() throws IOException
+        {
+            _endp.close();
+        }
+
+        public int fill(Buffer buffer) throws IOException
+        {
+            return _endp.fill(buffer);
+        }
+
+        public boolean isWritable()
+        {
+            return _endp.isWritable();
+        }
+
+        public boolean hasProgressed()
+        {
+            return _endp.hasProgressed();
+        }
+
+        public int flush(Buffer buffer) throws IOException
+        {
+            return _endp.flush(buffer);
+        }
+
+        public void scheduleTimeout(Task task, long timeoutMs)
+        {
+            _endp.scheduleTimeout(task,timeoutMs);
+        }
+
+        public void cancelTimeout(Task task)
+        {
+            _endp.cancelTimeout(task);
+        }
+
+        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+        {
+            return _endp.flush(header,buffer,trailer);
+        }
+
+        public String getLocalAddr()
+        {
+            return _endp.getLocalAddr();
+        }
+
+        public String getLocalHost()
+        {
+            return _endp.getLocalHost();
+        }
+
+        public int getLocalPort()
+        {
+            return _endp.getLocalPort();
+        }
+
+        public String getRemoteAddr()
+        {
+            return _endp.getRemoteAddr();
+        }
+
+        public String getRemoteHost()
+        {
+            return _endp.getRemoteHost();
+        }
+
+        public int getRemotePort()
+        {
+            return _endp.getRemotePort();
+        }
+
+        public boolean isBlocking()
+        {
+            return _endp.isBlocking();
+        }
+
+        public boolean blockReadable(long millisecs) throws IOException
+        {
+            return _endp.blockReadable(millisecs);
+        }
+
+        public boolean blockWritable(long millisecs) throws IOException
+        {
+            return _endp.blockWritable(millisecs);
+        }
+
+        public boolean isOpen()
+        {
+            return _endp.isOpen();
+        }
+
+        public Object getTransport()
+        {
+            return _endp.getTransport();
+        }
+
+        public void flush() throws IOException
+        {
+            _endp.flush();
+        }
+
+        public int getMaxIdleTime()
+        {
+            return _endp.getMaxIdleTime();
+        }
+
+        public void setMaxIdleTime(int timeMs) throws IOException
+        {
+            _endp.setMaxIdleTime(timeMs);
+        }
+
+        public void onIdleExpired(long idleForMs)
+        {
+            _endp.onIdleExpired(idleForMs);
+        }
+
+        public void setCheckForIdle(boolean check)
+        {
+            _endp.setCheckForIdle(check);
+        }
+
+        public boolean isCheckForIdle()
+        {
+            return _endp.isCheckForIdle();
+        }
+
+        public String toString()
+        {
+            return "Upgradable:"+_endp.toString();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/SocketConnector.java b/src/java/org/eclipse/jetty/client/SocketConnector.java
new file mode 100644
index 0000000..2058b33
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/SocketConnector.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Socket;
+import javax.net.SocketFactory;
+
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+class SocketConnector extends AbstractLifeCycle implements HttpClient.Connector
+{
+    private static final Logger LOG = Log.getLogger(SocketConnector.class);
+
+    /**
+     *
+     */
+    private final HttpClient _httpClient;
+
+    /**
+     * @param httpClient
+     */
+    SocketConnector(HttpClient httpClient)
+    {
+        _httpClient = httpClient;
+    }
+
+    public void startConnection(final HttpDestination destination) throws IOException
+    {
+        Socket socket= destination.isSecure()
+            ? destination.getSslContextFactory().newSslSocket()
+            : SocketFactory.getDefault().createSocket();
+
+        socket.setSoTimeout(0);
+        socket.setTcpNoDelay(true);
+
+        Address address = destination.isProxied() ? destination.getProxy() : destination.getAddress();
+        socket.connect(address.toSocketAddress(), _httpClient.getConnectTimeout());
+
+        final EndPoint endpoint=new SocketEndPoint(socket);
+
+        final AbstractHttpConnection connection=new BlockingHttpConnection(_httpClient.getRequestBuffers(),_httpClient.getResponseBuffers(),endpoint);
+        connection.setDestination(destination);
+        destination.onNewConnection(connection);
+        _httpClient.getThreadPool().dispatch(new Runnable()
+        {
+            public void run()
+            {
+                try
+                {
+                    Connection con = connection;
+                    while(true)
+                    {
+                        final Connection next = con.handle();
+                        if (next!=con)
+                        {
+                            con=next;
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                catch (IOException e)
+                {
+                    if (e instanceof InterruptedIOException)
+                        LOG.ignore(e);
+                    else
+                    {
+                        LOG.debug(e);
+                        destination.onException(e);
+                    }
+                }
+                finally
+                {
+                    try
+                    {
+                        destination.returnConnection(connection,true);
+                    }
+                    catch (IOException e)
+                    {
+                        LOG.debug(e);
+                    }
+                }
+            }
+        });
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/security/Authentication.java b/src/java/org/eclipse/jetty/client/security/Authentication.java
new file mode 100644
index 0000000..9210296
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/Authentication.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpExchange;
+
+/**
+ * Simple authentication interface that sets required fields on the exchange.
+ */
+public interface Authentication
+{
+    public void setCredentials( HttpExchange exchange) throws IOException;
+}
diff --git a/src/java/org/eclipse/jetty/client/security/BasicAuthentication.java b/src/java/org/eclipse/jetty/client/security/BasicAuthentication.java
new file mode 100644
index 0000000..170932a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/BasicAuthentication.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * Sets authentication headers for BASIC authentication challenges
+ * 
+ * 
+ */
+public class BasicAuthentication implements Authentication
+{
+    private Buffer _authorization;
+    
+    public BasicAuthentication(Realm realm) throws IOException
+    {
+        String authenticationString = "Basic " + B64Code.encode( realm.getPrincipal() + ":" + realm.getCredentials(), StringUtil.__ISO_8859_1);
+        _authorization= new ByteArrayBuffer(authenticationString);
+    }
+    
+    /**
+     * BASIC authentication is of the form
+     * 
+     * encoded credentials are of the form: username:password
+     * 
+     * 
+     */
+    public void setCredentials( HttpExchange exchange ) throws IOException
+    {
+        exchange.setRequestHeader( HttpHeaders.AUTHORIZATION_BUFFER, _authorization);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/security/DigestAuthentication.java b/src/java/org/eclipse/jetty/client/security/DigestAuthentication.java
new file mode 100644
index 0000000..059f112
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/DigestAuthentication.java
@@ -0,0 +1,141 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+
+public class DigestAuthentication implements Authentication
+{
+    private static final String NC = "00000001";
+    Realm securityRealm;
+    Map details;
+    
+    public DigestAuthentication(Realm realm, Map details)
+    {
+        this.securityRealm=realm;
+        this.details=details;
+    }
+    
+
+    public void setCredentials( HttpExchange exchange ) 
+    throws IOException
+    {        
+        StringBuilder buffer = new StringBuilder().append("Digest");
+        
+        buffer.append(" ").append("username").append('=').append('"').append(securityRealm.getPrincipal()).append('"');
+        
+        buffer.append(", ").append("realm").append('=').append('"').append(String.valueOf(details.get("realm"))).append('"');
+        
+        buffer.append(", ").append("nonce").append('=').append('"').append(String.valueOf(details.get("nonce"))).append('"');
+        
+        buffer.append(", ").append("uri").append('=').append('"').append(exchange.getURI()).append('"');
+        
+        buffer.append(", ").append("algorithm").append('=').append(String.valueOf(details.get("algorithm")));
+        
+        String cnonce = newCnonce(exchange, securityRealm, details);
+        
+        buffer.append(", ").append("response").append('=').append('"').append(newResponse(cnonce, 
+                exchange, securityRealm, details)).append('"');
+        
+        buffer.append(", ").append("qop").append('=').append(String.valueOf(details.get("qop")));
+        
+
+        buffer.append(", ").append("nc").append('=').append(NC);
+        
+        buffer.append(", ").append("cnonce").append('=').append('"').append(cnonce).append('"');
+        
+        exchange.setRequestHeader( HttpHeaders.AUTHORIZATION, 
+                new String(buffer.toString().getBytes(StringUtil.__ISO_8859_1)));
+    }
+    
+    protected String newResponse(String cnonce, HttpExchange exchange, Realm securityRealm, Map details)
+    {        
+        try{
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            
+            // calc A1 digest
+            md.update(securityRealm.getPrincipal().getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(String.valueOf(details.get("realm")).getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(securityRealm.getCredentials().getBytes(StringUtil.__ISO_8859_1));
+            byte[] ha1 = md.digest();
+            // calc A2 digest
+            md.reset();
+            md.update(exchange.getMethod().getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(exchange.getURI().getBytes(StringUtil.__ISO_8859_1));
+            byte[] ha2=md.digest();
+            
+            md.update(TypeUtil.toString(ha1,16).getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(String.valueOf(details.get("nonce")).getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(NC.getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(String.valueOf(details.get("qop")).getBytes(StringUtil.__ISO_8859_1));
+            md.update((byte)':');
+            md.update(TypeUtil.toString(ha2,16).getBytes(StringUtil.__ISO_8859_1));
+            byte[] digest=md.digest();
+            
+            // check digest
+            return encode(digest);
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }        
+    }
+    
+    protected String newCnonce(HttpExchange exchange, Realm securityRealm, Map details)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] b= md.digest(String.valueOf(System.currentTimeMillis()).getBytes(StringUtil.__ISO_8859_1));            
+            return encode(b);
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    private static String encode(byte[] data)
+    {
+        StringBuilder buffer = new StringBuilder();
+        for (int i=0; i<data.length; i++) 
+        {
+            buffer.append(Integer.toHexString((data[i] & 0xf0) >>> 4));
+            buffer.append(Integer.toHexString(data[i] & 0x0f));
+        }
+        return buffer.toString();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/client/security/HashRealmResolver.java b/src/java/org/eclipse/jetty/client/security/HashRealmResolver.java
new file mode 100644
index 0000000..8d3defc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/HashRealmResolver.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.client.HttpDestination;
+
+public class HashRealmResolver implements RealmResolver
+{
+    private Map<String, Realm>_realmMap;  
+    
+    public void addSecurityRealm( Realm realm )
+    {
+        if (_realmMap == null)
+        {
+            _realmMap = new HashMap<String, Realm>();
+        }
+        _realmMap.put( realm.getId(), realm );
+    }
+    
+    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException
+    {
+        return _realmMap.get( realmName );
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/client/security/ProxyAuthorization.java b/src/java/org/eclipse/jetty/client/security/ProxyAuthorization.java
new file mode 100644
index 0000000..4ad968b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/ProxyAuthorization.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * Sets proxy authentication headers for BASIC authentication challenges
+ * 
+ * 
+ */
+public class ProxyAuthorization implements Authentication
+{
+    private Buffer _authorization;
+    
+    public ProxyAuthorization(String username,String password) throws IOException
+    {
+        String authenticationString = "Basic " + B64Code.encode( username + ":" + password, StringUtil.__ISO_8859_1);
+        _authorization= new ByteArrayBuffer(authenticationString);
+    }
+    
+    /**
+     * BASIC proxy authentication is of the form
+     * 
+     * encoded credentials are of the form: username:password
+     * 
+     * 
+     */
+    public void setCredentials( HttpExchange exchange ) throws IOException
+    {
+        exchange.setRequestHeader( HttpHeaders.PROXY_AUTHORIZATION_BUFFER, _authorization);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/security/Realm.java b/src/java/org/eclipse/jetty/client/security/Realm.java
new file mode 100644
index 0000000..493f80e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/Realm.java
@@ -0,0 +1,32 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+/**
+ * Simple security realm interface.
+ */
+public interface Realm
+{
+    public String getId();
+
+    public String getPrincipal();
+
+    public String getCredentials();
+
+}
diff --git a/src/java/org/eclipse/jetty/client/security/RealmResolver.java b/src/java/org/eclipse/jetty/client/security/RealmResolver.java
new file mode 100644
index 0000000..a7fd989
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/RealmResolver.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpDestination;
+
+public interface RealmResolver
+{
+    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException;   
+}
diff --git a/src/java/org/eclipse/jetty/client/security/SecurityListener.java b/src/java/org/eclipse/jetty/client/security/SecurityListener.java
new file mode 100644
index 0000000..d3bd51d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/SecurityListener.java
@@ -0,0 +1,276 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpEventListenerWrapper;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * SecurityListener
+ * 
+ * Allow for insertion of security dialog when performing an
+ * HttpExchange.
+ */
+public class SecurityListener extends HttpEventListenerWrapper
+{
+    private static final Logger LOG = Log.getLogger(SecurityListener.class);
+	
+    private HttpDestination _destination;
+    private HttpExchange _exchange;
+    private boolean _requestComplete;
+    private boolean _responseComplete;  
+    private boolean _needIntercept;
+    
+    private int _attempts = 0; // TODO remember to settle on winning solution
+
+    public SecurityListener(HttpDestination destination, HttpExchange ex)
+    {
+        // Start of sending events through to the wrapped listener
+        // Next decision point is the onResponseStatus
+        super(ex.getEventListener(),true);
+        _destination=destination;
+        _exchange=ex;
+    }
+    
+    
+    /**
+     * scrapes an authentication type from the authString
+     * 
+     * @param authString
+     * @return the authentication type
+     */
+    protected String scrapeAuthenticationType( String authString )
+    {
+        String authType;
+
+        if ( authString.indexOf( " " ) == -1 )
+        {
+            authType = authString.toString().trim();
+        }
+        else
+        {
+            String authResponse = authString.toString();
+            authType = authResponse.substring( 0, authResponse.indexOf( " " ) ).trim();
+        }
+        return authType;
+    }
+    
+    /**
+     * scrapes a set of authentication details from the authString
+     * 
+     * @param authString
+     * @return the authentication details
+     */
+    protected Map<String, String> scrapeAuthenticationDetails( String authString )
+    {
+        Map<String, String> authenticationDetails = new HashMap<String, String>();
+        authString = authString.substring( authString.indexOf( " " ) + 1, authString.length() );
+        StringTokenizer strtok = new StringTokenizer( authString, ",");
+        
+        while ( strtok.hasMoreTokens() )
+        {
+            String token = strtok.nextToken();
+            String[] pair = token.split( "=" );
+            
+            // authentication details ought to come in two parts, if not then just skip
+            if ( pair.length == 2 )
+            {
+                String itemName = pair[0].trim();
+                String itemValue = pair[1].trim();
+                
+                itemValue = StringUtil.unquote( itemValue );
+                
+                authenticationDetails.put( itemName, itemValue );
+            }    
+            else
+            {
+                LOG.debug("SecurityListener: missed scraping authentication details - " + token );
+            }
+        }
+        return authenticationDetails;
+    }
+
+  
+    @Override
+    public void onResponseStatus( Buffer version, int status, Buffer reason )
+        throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("SecurityListener:Response Status: " + status );
+
+        if ( status == HttpStatus.UNAUTHORIZED_401 && _attempts<_destination.getHttpClient().maxRetries()) 
+        {
+            // Let's absorb events until we have done some retries
+            setDelegatingResponses(false);
+            _needIntercept = true;
+        }
+        else 
+        {
+            setDelegatingResponses(true);
+            setDelegatingRequests(true);
+            _needIntercept = false;
+        }
+        super.onResponseStatus(version,status,reason);
+    }
+
+
+    @Override
+    public void onResponseHeader( Buffer name, Buffer value )
+        throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug( "SecurityListener:Header: " + name.toString() + " / " + value.toString() );
+        
+        
+        if (!isDelegatingResponses())
+        {
+            int header = HttpHeaders.CACHE.getOrdinal(name);
+            switch (header)
+            {
+                case HttpHeaders.WWW_AUTHENTICATE_ORDINAL:
+
+                    // TODO don't hard code this bit.
+                    String authString = value.toString();
+                    String type = scrapeAuthenticationType( authString );                  
+
+                    // TODO maybe avoid this map creation
+                    Map<String,String> details = scrapeAuthenticationDetails( authString );
+                    String pathSpec="/"; // TODO work out the real path spec
+                    RealmResolver realmResolver = _destination.getHttpClient().getRealmResolver();
+                    
+                    if ( realmResolver == null )
+                    {
+                        break;
+                    }
+                    
+                    Realm realm = realmResolver.getRealm( details.get("realm"), _destination, pathSpec ); // TODO work our realm correctly 
+                    
+                    if ( realm == null )
+                    {
+                        LOG.warn( "Unknown Security Realm: " + details.get("realm") );
+                    }
+                    else if ("digest".equalsIgnoreCase(type))
+                    {
+                        _destination.addAuthorization("/",new DigestAuthentication(realm,details));
+                        
+                    }
+                    else if ("basic".equalsIgnoreCase(type))
+                    {
+                        _destination.addAuthorization(pathSpec,new BasicAuthentication(realm));
+                    }
+                    
+                    break;
+            }
+        }
+        super.onResponseHeader(name,value);
+    }
+    
+
+    @Override
+    public void onRequestComplete() throws IOException
+    {
+        _requestComplete = true;
+
+        if (_needIntercept)
+        {
+            if (_requestComplete && _responseComplete)
+            {
+               if (LOG.isDebugEnabled())
+                   LOG.debug("onRequestComplete, Both complete: Resending from onResponseComplete "+_exchange); 
+                _responseComplete = false;
+                _requestComplete = false;
+                setDelegatingRequests(true);
+                setDelegatingResponses(true);
+                _destination.resend(_exchange);  
+            } 
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("onRequestComplete, Response not yet complete onRequestComplete, calling super for "+_exchange);
+                super.onRequestComplete(); 
+            }
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("onRequestComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
+            super.onRequestComplete();
+        }
+    }
+
+
+    @Override
+    public void onResponseComplete() throws IOException
+    {   
+        _responseComplete = true;
+        if (_needIntercept)
+        {  
+            if (_requestComplete && _responseComplete)
+            {              
+                if (LOG.isDebugEnabled())
+                    LOG.debug("onResponseComplete, Both complete: Resending from onResponseComplete"+_exchange);
+                _responseComplete = false;
+                _requestComplete = false;
+                setDelegatingResponses(true);
+                setDelegatingRequests(true);
+                _destination.resend(_exchange); 
+
+            }
+            else
+            {
+               if (LOG.isDebugEnabled())
+                   LOG.debug("onResponseComplete, Request not yet complete from onResponseComplete,  calling super "+_exchange);
+                super.onResponseComplete(); 
+            }
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("OnResponseComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
+            super.onResponseComplete();  
+        }
+    }
+
+    @Override
+    public void onRetry()
+    {
+        _attempts++;
+        setDelegatingRequests(true);
+        setDelegatingResponses(true);
+        _requestComplete=false;
+        _responseComplete=false;
+        _needIntercept=false;
+        super.onRetry();
+    }  
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java b/src/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java
new file mode 100644
index 0000000..dca12d6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/security/SimpleRealmResolver.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpDestination;
+
+/**
+ * Simple Realm Resolver.
+ * <p> A Realm Resolver that wraps a single realm.
+ * 
+ *
+ */
+public class SimpleRealmResolver implements RealmResolver
+{
+    private Realm _realm;
+    
+    public SimpleRealmResolver( Realm realm )
+    {
+        _realm=realm;
+    }
+    
+    public Realm getRealm( String realmName, HttpDestination destination, String path ) throws IOException
+    {
+        return _realm;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/webdav/MkcolExchange.java b/src/java/org/eclipse/jetty/client/webdav/MkcolExchange.java
new file mode 100644
index 0000000..2f473a4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/webdav/MkcolExchange.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.webdav;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.CachedExchange;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+public class MkcolExchange extends CachedExchange
+{
+    private static final Logger LOG = Log.getLogger(MkcolExchange.class);
+
+    boolean exists = false;
+
+    public MkcolExchange()
+    {
+        super(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        if ( status == HttpStatus.CREATED_201 )
+        {
+            LOG.debug( "MkcolExchange:Status: Successfully created resource" );
+            exists = true;
+        }
+
+        if ( status == HttpStatus.METHOD_NOT_ALLOWED_405 ) // returned when resource exists
+        {
+            LOG.debug( "MkcolExchange:Status: Resource must exist" );
+            exists = true;
+        }
+
+        super.onResponseStatus(version, status, reason);
+    }
+
+    public boolean exists()
+    {
+        return exists;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/webdav/PropfindExchange.java b/src/java/org/eclipse/jetty/client/webdav/PropfindExchange.java
new file mode 100644
index 0000000..78c913f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/webdav/PropfindExchange.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.webdav;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+public class PropfindExchange extends HttpExchange
+{
+    private static final Logger LOG = Log.getLogger(PropfindExchange.class);
+
+    boolean _propertyExists = false;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        if ( status == HttpStatus.OK_200 )
+        {
+            LOG.debug( "PropfindExchange:Status: Exists" );
+            _propertyExists = true;
+        }
+        else
+        {
+            LOG.debug( "PropfindExchange:Status: Not Exists" );
+        }
+
+        super.onResponseStatus(version, status, reason);
+    }
+
+    public boolean exists()
+    {
+        return _propertyExists;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/client/webdav/WebdavListener.java b/src/java/org/eclipse/jetty/client/webdav/WebdavListener.java
new file mode 100644
index 0000000..8797f31
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/webdav/WebdavListener.java
@@ -0,0 +1,332 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.client.webdav;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpDestination;
+import org.eclipse.jetty.client.HttpEventListenerWrapper;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.client.security.SecurityListener;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * WebdavListener
+ * 
+ * 
+ * 
+ * 
+ */
+public class WebdavListener extends HttpEventListenerWrapper
+{
+    private static final Logger LOG = Log.getLogger(WebdavListener.class);
+
+    private HttpDestination _destination;
+    private HttpExchange _exchange;
+    private boolean _requestComplete;
+    private boolean _responseComplete; 
+    private boolean _webdavEnabled;
+    private boolean _needIntercept;
+
+    public WebdavListener(HttpDestination destination, HttpExchange ex)
+    {
+        // Start of sending events through to the wrapped listener
+        // Next decision point is the onResponseStatus
+        super(ex.getEventListener(),true);
+        _destination=destination;
+        _exchange=ex;
+
+        // We'll only enable webdav if this is a PUT request
+        if ( HttpMethods.PUT.equalsIgnoreCase( _exchange.getMethod() ) )
+        {
+            _webdavEnabled = true;
+        }
+    }
+
+    @Override
+    public void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+    {
+        if ( !_webdavEnabled )
+        {
+            _needIntercept = false;
+            super.onResponseStatus(version, status, reason);
+            return;
+        }
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("WebdavListener:Response Status: " + status );
+
+        // The dav spec says that CONFLICT should be returned when the parent collection doesn't exist but I am seeing
+        // FORBIDDEN returned instead so running with that.
+        if ( status == HttpStatus.FORBIDDEN_403 || status == HttpStatus.CONFLICT_409 )
+        {
+            if ( _webdavEnabled )
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("WebdavListener:Response Status: dav enabled, taking a stab at resolving put issue" );
+                setDelegatingResponses( false ); // stop delegating, we can try and fix this request
+                _needIntercept = true;
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("WebdavListener:Response Status: Webdav Disabled" );
+                setDelegatingResponses( true ); // just make sure we delegate
+                setDelegatingRequests( true );
+                _needIntercept = false;
+            }
+        }
+        else
+        {
+            _needIntercept = false;
+            setDelegatingResponses( true );
+            setDelegatingRequests( true );
+        }
+
+        super.onResponseStatus(version, status, reason);
+    }
+
+    @Override
+    public void onResponseComplete() throws IOException
+    {
+        _responseComplete = true;
+        if (_needIntercept)
+        {
+            if ( _requestComplete && _responseComplete)
+            {
+                try
+                {
+                    // we have some work to do before retrying this
+                    if ( resolveCollectionIssues() )
+                    {
+                        setDelegatingRequests( true );
+                        setDelegatingResponses(true);
+                        _requestComplete = false;
+                        _responseComplete = false;
+                        _destination.resend(_exchange);
+                    }
+                    else
+                    {
+                        // admit defeat but retry because someone else might have 
+                        setDelegationResult(false);
+                        setDelegatingRequests( true );
+                        setDelegatingResponses(true);
+                        super.onResponseComplete();
+                    }
+                }
+                catch ( IOException ioe )
+                {
+                    LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate");
+                    super.onResponseComplete();
+                }
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("WebdavListener:Not ready, calling super");
+                super.onResponseComplete();
+            }
+        }
+        else
+        {
+            super.onResponseComplete();
+        }
+    }
+
+    
+    
+    @Override
+    public void onRequestComplete () throws IOException
+    {
+        _requestComplete = true;
+        if (_needIntercept)
+        {
+            if ( _requestComplete && _responseComplete)
+            {
+                try
+                {
+                    // we have some work to do before retrying this
+                    if ( resolveCollectionIssues() )
+                    {
+                        setDelegatingRequests( true );
+                        setDelegatingResponses(true);
+                        _requestComplete = false;
+                        _responseComplete = false;
+                        _destination.resend(_exchange);
+                    }
+                    else
+                    {
+                        // admit defeat but retry because someone else might have 
+                        setDelegatingRequests( true );
+                        setDelegatingResponses(true);
+                        super.onRequestComplete();
+                    }
+                }
+                catch ( IOException ioe )
+                {
+                    LOG.debug("WebdavListener:Complete:IOException: might not be dealing with dav server, delegate");
+                    super.onRequestComplete();
+                }
+            }
+            else
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("WebdavListener:Not ready, calling super");
+                super.onRequestComplete();
+            }
+        }
+        else
+        {
+            super.onRequestComplete();
+        } 
+    }
+
+   
+    
+    
+    /**
+     * walk through the steps to try and resolve missing parent collection issues via webdav
+     *
+     * TODO this really ought to use URI itself for this resolution
+     *
+     * @return
+     * @throws IOException
+     */
+    private boolean resolveCollectionIssues() throws IOException
+    {
+
+        String uri = _exchange.getURI();
+        String[] uriCollection = _exchange.getURI().split("/");
+        int checkNum = uriCollection.length;
+        int rewind = 0;
+
+        String parentUri = URIUtil.parentPath( uri );
+        while ( parentUri != null && !checkExists(parentUri) )
+        {
+            ++rewind;
+            parentUri = URIUtil.parentPath( parentUri );
+        }
+
+        // confirm webdav is supported for this collection
+        if ( checkWebdavSupported() )
+        {
+            for (int i = 0; i < rewind;)
+            {
+                makeCollection(parentUri + "/" + uriCollection[checkNum - rewind - 1]);
+                parentUri = parentUri + "/" + uriCollection[checkNum - rewind - 1];
+                --rewind;
+            }
+        }
+        else
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean checkExists( String uri ) throws IOException
+    {
+        if (uri == null)
+        {
+            System.out.println("have failed miserably");
+            return false;
+        }
+        
+        PropfindExchange propfindExchange = new PropfindExchange();
+        propfindExchange.setAddress( _exchange.getAddress() );
+        propfindExchange.setMethod( HttpMethods.GET ); // PROPFIND acts wonky, just use get
+        propfindExchange.setScheme( _exchange.getScheme() );
+        propfindExchange.setEventListener( new SecurityListener( _destination, propfindExchange ) );
+        propfindExchange.setConfigureListeners( false );
+        propfindExchange.setRequestURI( uri );
+
+        _destination.send( propfindExchange );
+
+        try
+        {
+            propfindExchange.waitForDone();
+
+            return propfindExchange.exists();
+        }
+        catch ( InterruptedException ie )
+        {
+            LOG.ignore( ie );                  
+            return false;
+        }
+    }
+
+    private boolean makeCollection( String uri ) throws IOException
+    {
+        MkcolExchange mkcolExchange = new MkcolExchange();
+        mkcolExchange.setAddress( _exchange.getAddress() );
+        mkcolExchange.setMethod( "MKCOL " + uri + " HTTP/1.1" );
+        mkcolExchange.setScheme( _exchange.getScheme() );
+        mkcolExchange.setEventListener( new SecurityListener( _destination, mkcolExchange ) );
+        mkcolExchange.setConfigureListeners( false );
+        mkcolExchange.setRequestURI( uri );
+
+        _destination.send( mkcolExchange );
+
+        try
+        {
+            mkcolExchange.waitForDone();
+
+            return mkcolExchange.exists();
+        }
+        catch ( InterruptedException ie )
+        {
+            LOG.ignore( ie );
+            return false;
+        }
+    }
+
+    
+    private boolean checkWebdavSupported() throws IOException
+    {
+        WebdavSupportedExchange supportedExchange = new WebdavSupportedExchange();
+        supportedExchange.setAddress( _exchange.getAddress() );
+        supportedExchange.setMethod( HttpMethods.OPTIONS );
+        supportedExchange.setScheme( _exchange.getScheme() );
+        supportedExchange.setEventListener( new SecurityListener( _destination, supportedExchange ) );
+        supportedExchange.setConfigureListeners( false );
+        supportedExchange.setRequestURI( _exchange.getURI() );
+
+        _destination.send( supportedExchange );
+
+        try
+        {
+            supportedExchange.waitTilCompletion();
+            return supportedExchange.isWebdavSupported();
+        }
+        catch (InterruptedException ie )
+        {            
+            LOG.ignore( ie );
+            return false;
+        }
+
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java b/src/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java
new file mode 100644
index 0000000..7b4c237
--- /dev/null
+++ b/src/java/org/eclipse/jetty/client/webdav/WebdavSupportedExchange.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.client.webdav;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+public class WebdavSupportedExchange extends HttpExchange
+{
+    private static final Logger LOG = Log.getLogger(WebdavSupportedExchange.class);
+
+    private boolean _webdavSupported = false;
+    private boolean _isComplete = false;
+
+    @Override
+    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("WebdavSupportedExchange:Header:" + name.toString() + " / " + value.toString() );
+        if ( "DAV".equals( name.toString() ) )
+        {
+            if ( value.toString().indexOf( "1" ) >= 0 || value.toString().indexOf( "2" ) >= 0 )
+            {
+                _webdavSupported = true;
+            }
+        }
+
+        super.onResponseHeader(name, value);
+    }
+
+    public void waitTilCompletion() throws InterruptedException
+    {
+        synchronized (this)
+        {
+            while ( !_isComplete)
+            {
+                this.wait();
+            }
+        }
+    }
+
+    @Override
+    protected void onResponseComplete() throws IOException
+    {
+        _isComplete = true;
+
+        super.onResponseComplete();
+    }
+
+    public boolean isWebdavSupported()
+    {
+        return _webdavSupported;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/continuation/Continuation.java b/src/java/org/eclipse/jetty/continuation/Continuation.java
new file mode 100644
index 0000000..0eb8ca0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/Continuation.java
@@ -0,0 +1,420 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+
+/* ------------------------------------------------------------ */
+/**
+ * Continuation.
+ * 
+ * A continuation is a mechanism by which a HTTP Request can be suspended and
+ * restarted after a timeout or an asynchronous event has occurred.
+ * <p>
+ * The continuation mechanism is a portable mechanism that will work 
+ * asynchronously without additional configuration of all jetty-7, 
+ * jetty-8 and Servlet 3.0 containers.   With the addition of 
+ * the {@link ContinuationFilter}, the mechanism will also work
+ * asynchronously on jetty-6 and non-asynchronously on any 
+ * servlet 2.5 container.
+ * <p>
+ * The Continuation API is a simplification of the richer async API
+ * provided by the servlet-3.0 and an enhancement of the continuation
+ * API that was introduced with jetty-6. 
+ * </p>
+ * <h1>Continuation Usage</h1>
+ * <p>
+ * A continuation object is obtained for a request by calling the 
+ * factory method {@link ContinuationSupport#getContinuation(ServletRequest)}.
+ * The continuation type returned will depend on the servlet container
+ * being used.
+ * </p> 
+ * <p>
+ * There are two distinct style of operation of the continuation API.
+ * </p>
+ * <h3>Suspend/Resume Usage</h3> 
+ * <p>The suspend/resume style is used when a servlet and/or
+ * filter is used to generate the response after a asynchronous wait that is
+ * terminated by an asynchronous handler.
+ * </p>
+ * <pre>
+ * <b>Filter/Servlet:</b>
+ *   // if we need to get asynchronous results
+ *   Object results = request.getAttribute("results);
+ *   if (results==null)
+ *   {
+ *     Continuation continuation = ContinuationSupport.getContinuation(request);
+ *     continuation.suspend();
+ *     myAsyncHandler.register(continuation);
+ *     return; // or continuation.undispatch();
+ *   }
+ * 
+ * async wait ...
+ * 
+ * <b>Async Handler:</b>
+ *   // when the waited for event happens
+ *   continuation.setAttribute("results",event);
+ *   continuation.resume();
+ *   
+ * <b>Filter/Servlet:</b>
+ *   // when the request is redispatched 
+ *   if (results==null)
+ *   {
+ *     ... // see above
+ *   }
+ *   else
+ *   {
+ *     response.getOutputStream().write(process(results));
+ *   }
+ * </pre> 
+ * <h3>Suspend/Complete Usage</h3> 
+ * <p>
+ * The suspend/complete style is used when an asynchronous handler is used to 
+ * generate the response:
+ * </p>
+ * <pre>
+ * <b>Filter/Servlet:</b>
+ *   // when we want to enter asynchronous mode
+ *   Continuation continuation = ContinuationSupport.getContinuation(request);
+ *   continuation.suspend(response); // response may be wrapped
+ *   myAsyncHandler.register(continuation);
+ *   return; // or continuation.undispatch();
+ *
+ * <b>Wrapping Filter:</b>
+ *   // any filter that had wrapped the response should be implemented like:
+ *   try
+ *   {
+ *     chain.doFilter(request,wrappedResponse);
+ *   }
+ *   finally
+ *   {
+ *     if (!continuation.isResponseWrapped())
+ *       wrappedResponse.finish()
+ *     else
+ *       continuation.addContinuationListener(myCompleteListener)
+ *   }
+ *
+ * async wait ...
+ *
+ * <b>Async Handler:</b>
+ *   // when the async event happens
+ *   continuation.getServletResponse().getOutputStream().write(process(event));
+ *   continuation.complete()
+ * </pre>
+ * 
+ * <h1>Continuation Timeout</h1>
+ * <p>
+ * If a continuation is suspended, but neither {@link #complete()} or {@link #resume()} is
+ * called during the period set by {@link #setTimeout(long)}, then the continuation will
+ * expire and {@link #isExpired()} will return true. 
+ * </p>
+ * <p>
+ * When a continuation expires, the {@link ContinuationListener#onTimeout(Continuation)}
+ * method is called on any {@link ContinuationListener} that has been registered via the
+ * {@link #addContinuationListener(ContinuationListener)} method. The onTimeout handlers 
+ * may write a response and call {@link #complete()}. If {@link #complete()} is not called, 
+ * then the container will redispatch the request as if {@link #resume()} had been called,
+ * except that {@link #isExpired()} will be true and {@link #isResumed()} will be false.
+ * </p>
+ * 
+ * @see ContinuationSupport
+ * @see ContinuationListener
+ * 
+ */
+public interface Continuation
+{
+    public final static String ATTRIBUTE = "org.eclipse.jetty.continuation";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the continuation timeout.
+     * 
+     * @param timeoutMs
+     *            The time in milliseconds to wait before expiring this
+     *            continuation after a call to {@link #suspend()} or {@link #suspend(ServletResponse)}.
+     *            A timeout of <=0 means the continuation will never expire.
+     */
+    void setTimeout(long timeoutMs);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Suspend the processing of the request and associated
+     * {@link ServletResponse}.
+     * 
+     * <p>
+     * After this method has been called, the lifecycle of the request will be
+     * extended beyond the return to the container from the
+     * {@link Servlet#service(ServletRequest, ServletResponse)} method and
+     * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
+     * calls. When a suspended request is returned to the container after
+     * a dispatch, then the container will not commit the associated response
+     * (unless an exception other than {@link ContinuationThrowable} is thrown).
+     * </p>
+     * 
+     * <p>
+     * When the thread calling the filter chain and/or servlet has returned to
+     * the container with a suspended request, the thread is freed for other
+     * tasks and the request is held until either:
+     * <ul>
+     * <li>a call to {@link #resume()}.</li>
+     * <li>a call to {@link #complete()}.</li>
+     * <li>the timeout expires.</li>
+     * </ul>
+     * <p>
+     * Typically suspend with no arguments is uses when a call to {@link #resume()}
+     * is expected. If a call to {@link #complete()} is expected, then the 
+     * {@link #suspend(ServletResponse)} method should be used instead of this method.
+     * </p>
+     * 
+     * @exception IllegalStateException
+     *                If the request cannot be suspended
+     */
+    void suspend();
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Suspend the processing of the request and associated
+     * {@link ServletResponse}.
+     * 
+     * <p>
+     * After this method has been called, the lifecycle of the request will be
+     * extended beyond the return to the container from the
+     * {@link Servlet#service(ServletRequest, ServletResponse)} method and
+     * {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)}
+     * calls. When a suspended request is returned to the container after
+     * a dispatch, then the container will not commit the associated response
+     * (unless an exception other than {@link ContinuationThrowable} is thrown).
+     * </p>
+     * <p>
+     * When the thread calling the filter chain and/or servlet has returned to
+     * the container with a suspended request, the thread is freed for other
+     * tasks and the request is held until either:
+     * <ul>
+     * <li>a call to {@link #resume()}.</li>
+     * <li>a call to {@link #complete()}.</li>
+     * <li>the timeout expires.</li>
+     * </ul>
+     * <p>
+     * Typically suspend with a response argument is uses when a call to {@link #complete()}
+     * is expected. If a call to {@link #resume()} is expected, then the 
+     * {@link #suspend()} method should be used instead of this method.
+     * </p>
+     * <p>
+     * Filters that may wrap the response object should check {@link #isResponseWrapped()}
+     * to decide if they should destroy/finish the wrapper. If {@link #isResponseWrapped()}
+     * returns true, then the wrapped request has been passed to the asynchronous
+     * handler and the wrapper should not be destroyed/finished until after a call to 
+     * {@link #complete()} (potentially using a {@link ContinuationListener#onComplete(Continuation)}
+     * listener).
+     * 
+     * @param response The response to return via a call to {@link #getServletResponse()}
+     * @exception IllegalStateException
+     *                If the request cannot be suspended
+     */
+    void suspend(ServletResponse response);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Resume a suspended request.
+     * 
+     * <p>
+     * This method can be called by any thread that has been passed a reference
+     * to a continuation. When called the request is redispatched to the
+     * normal filter chain and servlet processing with {@link #isInitial()} false.
+     * </p>
+     * <p>
+     * If resume is called before a suspended request is returned to the
+     * container (ie the thread that called {@link #suspend()} is still
+     * within the filter chain and/or servlet service method), then the resume
+     * does not take effect until the call to the filter chain and/or servlet
+     * returns to the container. In this case both {@link #isSuspended()} and
+     * {@link #isResumed()} return true. Multiple calls to resume are ignored.
+     * </p>
+     * <p>
+     * Typically resume() is used after a call to {@link #suspend()} with
+     * no arguments. The dispatch after a resume call will use the original
+     * request and response objects, even if {@link #suspend(ServletResponse)} 
+     * had been passed a wrapped response.
+     * </p>
+     * 
+     * @see #suspend()
+     * @exception IllegalStateException if the request is not suspended.
+     * 
+     */
+    void resume();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Complete a suspended request.
+     * 
+     * <p>
+     * This method can be called by any thread that has been passed a reference
+     * to a suspended request. When a request is completed, the associated
+     * response object committed and flushed. The request is not redispatched.
+     * </p>
+     * 
+     * <p>
+     * If complete is called before a suspended request is returned to the
+     * container (ie the thread that called {@link #suspend()} is still
+     * within the filter chain and/or servlet service method), then the complete
+     * does not take effect until the call to the filter chain and/or servlet
+     * returns to the container. In this case both {@link #isSuspended()} and
+     * {@link #isResumed()} return true.
+     * </p>
+     * 
+     * <p>
+     * Typically resume() is used after a call to {@link #suspend(ServletResponse)} with
+     * a possibly wrapped response. The async handler should use the response
+     * provided by {@link #getServletResponse()} to write the response before
+     * calling {@link #complete()}. If the request was suspended with a 
+     * call to {@link #suspend()} then no response object will be available via
+     * {@link #getServletResponse()}.
+     * </p>
+     * 
+     * <p>
+     * Once complete has been called and any thread calling the filter chain
+     * and/or servlet chain has returned to the container, the request lifecycle
+     * is complete. The container is able to recycle request objects, so it is
+     * not valid hold a request or continuation reference after the end of the 
+     * life cycle.
+     * 
+     * @see #suspend()
+     * @exception IllegalStateException
+     *                if the request is not suspended.
+     * 
+     */
+    void complete();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true after {@link #suspend()} has been called and before the
+     *         request has been redispatched due to being resumed, completed or
+     *         timed out.
+     */
+    boolean isSuspended();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the request has been redispatched by a call to
+     *         {@link #resume()}. Returns false after any subsequent call to
+     *         suspend
+     */
+    boolean isResumed();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true after a request has been redispatched as the result of a
+     *         timeout. Returns false after any subsequent call to suspend.
+     */
+    boolean isExpired();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true while the request is within the initial dispatch to the
+     *         filter chain and/or servlet. Will return false once the calling
+     *         thread has returned to the container after suspend has been
+     *         called and during any subsequent redispatch.
+     */
+    boolean isInitial();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Is the suspended response wrapped.
+     * <p>
+     * Filters that wrap the response object should check this method to 
+     * determine if they should destroy/finish the wrapped response. If 
+     * the request was suspended with a call to {@link #suspend(ServletResponse)}
+     * that passed the wrapped response, then the filter should register
+     * a {@link ContinuationListener} to destroy/finish the wrapped response
+     * during a call to {@link ContinuationListener#onComplete(Continuation)}.
+     * @return True if {@link #suspend(ServletResponse)} has been passed a
+     * {@link ServletResponseWrapper} instance.
+     */
+    boolean isResponseWrapped();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the suspended response.
+     * @return the {@link ServletResponse} passed to {@link #suspend(ServletResponse)}.
+     */
+    ServletResponse getServletResponse();
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Add a ContinuationListener.
+     * 
+     * @param listener
+     */
+    void addContinuationListener(ContinuationListener listener);
+    
+    /* ------------------------------------------------------------ */
+    /** Set a request attribute.
+     * This method is a convenience method to call the {@link ServletRequest#setAttribute(String, Object)}
+     * method on the associated request object.
+     * This is a thread safe call and may be called by any thread.
+     * @param name the attribute name
+     * @param attribute the attribute value
+     */
+    public void setAttribute(String name, Object attribute);
+    
+    /* ------------------------------------------------------------ */
+    /** Get a request attribute.
+     * This method is a convenience method to call the {@link ServletRequest#getAttribute(String)}
+     * method on the associated request object.
+     * This is a thread safe call and may be called by any thread.
+     * @param name the attribute name
+     * @return the attribute value
+     */
+    public Object getAttribute(String name);
+    
+    /* ------------------------------------------------------------ */
+    /** Remove a request attribute.
+     * This method is a convenience method to call the {@link ServletRequest#removeAttribute(String)}
+     * method on the associated request object.
+     * This is a thread safe call and may be called by any thread.
+     * @param name the attribute name
+     */
+    public void removeAttribute(String name);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Undispatch the request.
+     * <p>
+     * This method can be called on a suspended continuation in order
+     * to exit the dispatch to the filter/servlet by throwing a {@link ContinuationThrowable}
+     * which is caught either by the container or the {@link ContinuationFilter}.
+     * This is an alternative to simply returning from the dispatch in the case
+     * where filters in the filter chain may not be prepared to handle a suspended
+     * request.
+     * </p>
+     * This method should only be used as a last resort and a normal return is a prefereable
+     * solution if filters can be updated to handle that case.
+     * 
+     * @throws ContinuationThrowable thrown if the request is suspended. The instance of the 
+     * exception may be reused on subsequent calls, so the stack frame may not be accurate.
+     */
+    public void undispatch() throws ContinuationThrowable;
+}
diff --git a/src/java/org/eclipse/jetty/continuation/ContinuationFilter.java b/src/java/org/eclipse/jetty/continuation/ContinuationFilter.java
new file mode 100644
index 0000000..2500463
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/ContinuationFilter.java
@@ -0,0 +1,174 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * <p>ContinuationFilter must be applied to servlet paths that make use of
+ * the asynchronous features provided by {@link Continuation} APIs, but that
+ * are deployed in servlet containers that are neither Jetty (>= 7) nor a
+ * compliant Servlet 3.0 container.</p>
+ * <p>The following init parameters may be used to configure the filter (these are mostly for testing):</p>
+ * <dl>
+ * <dt>debug</dt><dd>Boolean controlling debug output</dd>
+ * <dt>jetty6</dt><dd>Boolean to force use of Jetty 6 continuations</dd>
+ * <dt>faux</dt><dd>Boolean to force use of faux continuations</dd>
+ * </dl>
+ * <p>If the servlet container is not Jetty (either 6 or 7) nor a Servlet 3
+ * container, then "faux" continuations will be used.</p>
+ * <p>Faux continuations will just put the thread that called {@link Continuation#suspend()}
+ * in wait, and will notify that thread when {@link Continuation#resume()} or
+ * {@link Continuation#complete()} is called.</p>
+ * <p>Faux continuations are not threadless continuations (they are "faux" - fake - for this reason)
+ * and as such they will scale less than proper continuations.</p>
+ */
+public class ContinuationFilter implements Filter
+{
+    static boolean _initialized;
+    static boolean __debug; // shared debug status
+    private boolean _faux;
+    private boolean _jetty6;
+    private boolean _filtered;
+    ServletContext _context;
+    private boolean _debug;
+
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        boolean jetty_7_or_greater="org.eclipse.jetty.servlet".equals(filterConfig.getClass().getPackage().getName());
+        _context = filterConfig.getServletContext();
+
+        String param=filterConfig.getInitParameter("debug");
+        _debug=param!=null&&Boolean.parseBoolean(param);
+        if (_debug)
+            __debug=true;
+
+        param=filterConfig.getInitParameter("jetty6");
+        if (param==null)
+            param=filterConfig.getInitParameter("partial");
+        if (param!=null)
+            _jetty6=Boolean.parseBoolean(param);
+        else
+            _jetty6=ContinuationSupport.__jetty6 && !jetty_7_or_greater;
+
+        param=filterConfig.getInitParameter("faux");
+        if (param!=null)
+            _faux=Boolean.parseBoolean(param);
+        else
+            _faux=!(jetty_7_or_greater || _jetty6 || _context.getMajorVersion()>=3);
+
+        _filtered=_faux||_jetty6;
+        if (_debug)
+            _context.log("ContinuationFilter "+
+                    " jetty="+jetty_7_or_greater+
+                    " jetty6="+_jetty6+
+                    " faux="+_faux+
+                    " filtered="+_filtered+
+                    " servlet3="+ContinuationSupport.__servlet3);
+        _initialized=true;
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        if (_filtered)
+        {
+            Continuation c = (Continuation) request.getAttribute(Continuation.ATTRIBUTE);
+            FilteredContinuation fc;
+            if (_faux && (c==null || !(c instanceof FauxContinuation)))
+            {
+                fc = new FauxContinuation(request);
+                request.setAttribute(Continuation.ATTRIBUTE,fc);
+            }
+            else
+                fc=(FilteredContinuation)c;
+
+            boolean complete=false;
+            while (!complete)
+            {
+                try
+                {
+                    if (fc==null || (fc).enter(response))
+                        chain.doFilter(request,response);
+                }
+                catch (ContinuationThrowable e)
+                {
+                    debug("faux",e);
+                }
+                finally
+                {
+                    if (fc==null)
+                        fc = (FilteredContinuation) request.getAttribute(Continuation.ATTRIBUTE);
+
+                    complete=fc==null || (fc).exit();
+                }
+            }
+        }
+        else
+        {
+            try
+            {
+                chain.doFilter(request,response);
+            }
+            catch (ContinuationThrowable e)
+            {
+                debug("caught",e);
+            }
+        }
+    }
+
+    private void debug(String string)
+    {
+        if (_debug)
+        {
+            _context.log(string);
+        }
+    }
+
+    private void debug(String string, Throwable th)
+    {
+        if (_debug)
+        {
+            if (th instanceof ContinuationThrowable)
+                _context.log(string+":"+th);
+            else
+                _context.log(string,th);
+        }
+    }
+
+    public void destroy()
+    {
+    }
+
+    public interface FilteredContinuation extends Continuation
+    {
+        boolean enter(ServletResponse response);
+        boolean exit();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/continuation/ContinuationListener.java b/src/java/org/eclipse/jetty/continuation/ContinuationListener.java
new file mode 100644
index 0000000..e4e9ca1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/ContinuationListener.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.util.EventListener;
+
+import javax.servlet.ServletRequestListener;
+
+
+/* ------------------------------------------------------------ */
+/** A Continuation Listener
+ * <p>
+ * A ContinuationListener may be registered with a call to
+ * {@link Continuation#addContinuationListener(ContinuationListener)}.
+ * 
+ */
+public interface ContinuationListener extends EventListener 
+{    
+    /* ------------------------------------------------------------ */
+    /**
+     * Called when a continuation life cycle is complete and after
+     * any calls to {@link ServletRequestListener#requestDestroyed(javax.servlet.ServletRequestEvent)}
+     * The response may still be written to during the call.
+     * 
+     * @param continuation
+     */
+    public void onComplete(Continuation continuation);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Called when a suspended continuation has timed out.
+     * The response may be written to and the methods 
+     * {@link Continuation#resume()} or {@link Continuation#complete()} 
+     * may be called by a onTimeout implementation,
+     * @param continuation
+     */
+    public void onTimeout(Continuation continuation);
+
+}
diff --git a/src/java/org/eclipse/jetty/continuation/ContinuationSupport.java b/src/java/org/eclipse/jetty/continuation/ContinuationSupport.java
new file mode 100644
index 0000000..b4c6210
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/ContinuationSupport.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.lang.reflect.Constructor;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.ServletResponse;
+
+/* ------------------------------------------------------------ */
+/** ContinuationSupport.
+ *
+ * Factory class for accessing Continuation instances, which with either be
+ * native to the container (jetty >= 6), a servlet 3.0 or a faux continuation.
+ *
+ */
+public class ContinuationSupport
+{
+    static final boolean __jetty6;
+    static final boolean __servlet3;
+    static final Class<?> __waitingContinuation;
+    static final Constructor<? extends Continuation> __newServlet3Continuation;
+    static final Constructor<? extends Continuation> __newJetty6Continuation;
+    static
+    {
+        boolean servlet3Support=false;
+        Constructor<? extends Continuation>s3cc=null;
+        try
+        {
+            boolean servlet3=ServletRequest.class.getMethod("startAsync")!=null;
+            if (servlet3)
+            {
+                Class<? extends Continuation> s3c = ContinuationSupport.class.getClassLoader().loadClass("org.eclipse.jetty.continuation.Servlet3Continuation").asSubclass(Continuation.class);
+                s3cc=s3c.getConstructor(ServletRequest.class);
+                servlet3Support=true;
+            }
+        }
+        catch (Exception e)
+        {}
+        finally
+        {
+            __servlet3=servlet3Support;
+            __newServlet3Continuation=s3cc;
+        }
+
+        boolean jetty6Support=false;
+        Constructor<? extends Continuation>j6cc=null;
+        try
+        {
+            Class<?> jetty6ContinuationClass = ContinuationSupport.class.getClassLoader().loadClass("org.mortbay.util.ajax.Continuation");
+            boolean jetty6=jetty6ContinuationClass!=null;
+            if (jetty6)
+            {
+                Class<? extends Continuation> j6c = ContinuationSupport.class.getClassLoader().loadClass("org.eclipse.jetty.continuation.Jetty6Continuation").asSubclass(Continuation.class);
+                j6cc=j6c.getConstructor(ServletRequest.class, jetty6ContinuationClass);
+                jetty6Support=true;
+            }
+        }
+        catch (Exception e)
+        {}
+        finally
+        {
+            __jetty6=jetty6Support;
+            __newJetty6Continuation=j6cc;
+        }
+
+        Class<?> waiting=null;
+        try
+        {
+            waiting=ContinuationSupport.class.getClassLoader().loadClass("org.mortbay.util.ajax.WaitingContinuation");
+        }
+        catch (Exception e)
+        {
+        }
+        finally
+        {
+            __waitingContinuation=waiting;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a Continuation.  The type of the Continuation returned may
+     * vary depending on the container in which the application is
+     * deployed. It may be an implementation native to the container (eg
+     * org.eclipse.jetty.server.AsyncContinuation) or one of the utility
+     * implementations provided such as an internal <code>FauxContinuation</code>
+     * or a real implementation like {@link org.eclipse.jetty.continuation.Servlet3Continuation}.
+     * @param request The request
+     * @return a Continuation instance
+     */
+    public static Continuation getContinuation(ServletRequest request)
+    {
+        Continuation continuation = (Continuation) request.getAttribute(Continuation.ATTRIBUTE);
+        if (continuation!=null)
+            return continuation;
+
+        while (request instanceof ServletRequestWrapper)
+            request=((ServletRequestWrapper)request).getRequest();
+
+        if (__servlet3 )
+        {
+            try
+            {
+                continuation=__newServlet3Continuation.newInstance(request);
+                request.setAttribute(Continuation.ATTRIBUTE,continuation);
+                return continuation;
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        if (__jetty6)
+        {
+            Object c=request.getAttribute("org.mortbay.jetty.ajax.Continuation");
+            try
+            {
+                if (c==null || __waitingContinuation==null || __waitingContinuation.isInstance(c))
+                    continuation=new FauxContinuation(request);
+                else
+                    continuation= __newJetty6Continuation.newInstance(request,c);
+                request.setAttribute(Continuation.ATTRIBUTE,continuation);
+                return continuation;
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        throw new IllegalStateException("!(Jetty || Servlet 3.0 || ContinuationFilter)");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request the servlet request
+     * @param response the servlet response
+     * @deprecated use {@link #getContinuation(ServletRequest)}
+     * @return the continuation
+     */
+    @Deprecated
+    public static Continuation getContinuation(final ServletRequest request, final ServletResponse response)
+    {
+        return getContinuation(request);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/continuation/ContinuationThrowable.java b/src/java/org/eclipse/jetty/continuation/ContinuationThrowable.java
new file mode 100644
index 0000000..87c9f71
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/ContinuationThrowable.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.continuation;
+
+
+/* ------------------------------------------------------------ */
+/** ContinuationThrowable
+ * <p>
+ * A ContinuationThrowable is throw by {@link Continuation#undispatch()}
+ * in order to exit the dispatch to a Filter or Servlet.  Use of
+ * ContinuationThrowable is discouraged and it is preferable to 
+ * allow return to be used. ContinuationThrowables should only be
+ * used when there is a Filter/Servlet which cannot be modified
+ * to avoid committing a response when {@link Continuation#isSuspended()}
+ * is true.
+ * </p>
+ * <p>
+ * ContinuationThrowable instances are often reused so that the
+ * stack trace may be entirely unrelated to the calling stack.
+ * A real stack trace may be obtained by enabling debug.
+ * </p>
+ * <p>
+ * ContinuationThrowable extends Error as this is more likely
+ * to be uncaught (or rethrown) by a Filter/Servlet.  A ContinuationThrowable
+ * does not represent and error condition.
+ * </p>
+ */
+public class ContinuationThrowable extends Error
+{}
diff --git a/src/java/org/eclipse/jetty/continuation/FauxContinuation.java b/src/java/org/eclipse/jetty/continuation/FauxContinuation.java
new file mode 100644
index 0000000..777166d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/FauxContinuation.java
@@ -0,0 +1,508 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.continuation;
+
+import java.util.ArrayList;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+
+import org.eclipse.jetty.continuation.ContinuationFilter.FilteredContinuation;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A blocking implementation of Continuation.
+ * This implementation of Continuation is used by the {@link ContinuationFilter}
+ * when there are is no native or asynchronous continuation type available. 
+ */
+class FauxContinuation implements FilteredContinuation
+{
+    // common exception used for all continuations.  
+    // Turn on debug in ContinuationFilter to see real stack trace.
+    private final static ContinuationThrowable __exception = new ContinuationThrowable();
+    
+    private static final int __HANDLING=1;   // Request dispatched to filter/servlet
+    private static final int __SUSPENDING=2;   // Suspend called, but not yet returned to container
+    private static final int __RESUMING=3;     // resumed while suspending
+    private static final int __COMPLETING=4;   // resumed while suspending or suspended
+    private static final int __SUSPENDED=5;    // Suspended and parked
+    private static final int __UNSUSPENDING=6;
+    private static final int __COMPLETE=7;
+
+    private final ServletRequest _request;
+    private ServletResponse _response;
+    
+    private int _state=__HANDLING;
+    private boolean _initial=true;
+    private boolean _resumed=false;
+    private boolean _timeout=false;
+    private boolean _responseWrapped=false;
+    private  long _timeoutMs=30000; // TODO configure
+    
+    private ArrayList<ContinuationListener> _listeners; 
+
+    FauxContinuation(final ServletRequest request)
+    {
+        _request=request;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onComplete()
+    {
+        if (_listeners!=null)
+            for (ContinuationListener l:_listeners)
+                l.onComplete(this);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void onTimeout()
+    {
+        if (_listeners!=null)
+            for (ContinuationListener l:_listeners)
+                l.onTimeout(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
+     */
+    public boolean isResponseWrapped()
+    {
+        return _responseWrapped;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResumed()
+    {
+        synchronized(this)
+        {
+            return _resumed;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    return false;
+                case __SUSPENDING:
+                case __RESUMING:
+                case __COMPLETING:
+                case __SUSPENDED:
+                    return true;
+                case __UNSUSPENDING:
+                default:
+                    return false;   
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isExpired()
+    {
+        synchronized(this)
+        {
+            return _timeout;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeout(long timeoutMs)
+    {
+        _timeoutMs = timeoutMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void suspend(ServletResponse response)
+    {
+        _response=response;
+        _responseWrapped=response instanceof ServletResponseWrapper;
+        suspend();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void suspend()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    _timeout=false;
+                    _resumed=false;
+                    _state=__SUSPENDING;
+                    return;
+
+                case __SUSPENDING:
+                case __RESUMING:
+                    return;
+
+                case __COMPLETING:
+                case __SUSPENDED:
+                case __UNSUSPENDING:
+                    throw new IllegalStateException(this.getStatusString());
+
+                default:
+                    throw new IllegalStateException(""+_state);
+            }
+
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see org.mortbay.jetty.Suspendor#resume()
+     */
+    public void resume()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    _resumed=true;
+                    return;
+                    
+                case __SUSPENDING:
+                    _resumed=true;
+                    _state=__RESUMING;
+                    return;
+
+                case __RESUMING:
+                case __COMPLETING:
+                    return;
+                    
+                case __SUSPENDED:
+                    fauxResume();
+                    _resumed=true;
+                    _state=__UNSUSPENDING;
+                    break;
+                    
+                case __UNSUSPENDING:
+                    _resumed=true;
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public void complete()
+    {
+        // just like resume, except don't set _resumed=true;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    throw new IllegalStateException(this.getStatusString());
+                    
+                case __SUSPENDING:
+                    _state=__COMPLETING;
+                    break;
+                    
+                case __RESUMING:
+                    break;
+
+                case __COMPLETING:
+                    return;
+                    
+                case __SUSPENDED:
+                    _state=__COMPLETING;
+                    fauxResume();
+                    break;
+                    
+                case __UNSUSPENDING:
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
+     */
+    public boolean enter(ServletResponse response)
+    {
+        _response=response;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
+     */
+    public ServletResponse getServletResponse()
+    {
+        return _response;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    void handling()
+    {
+        synchronized (this)
+        {
+            _responseWrapped=false;
+            switch(_state)
+            {
+                case __HANDLING:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __SUSPENDING:
+                case __RESUMING:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __COMPLETING:
+                    return;
+
+                case __SUSPENDED:
+                    fauxResume();
+                case __UNSUSPENDING:
+                    _state=__HANDLING;
+                    return;
+
+                default:
+                    throw new IllegalStateException(""+_state);
+            }
+
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if handling is complete
+     */
+    public boolean exit()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    _state=__COMPLETE;
+                    onComplete();
+                    return true;
+
+                case __SUSPENDING:
+                    _initial=false;
+                    _state=__SUSPENDED;
+                    fauxSuspend(); // could block and change state.
+                    if (_state==__SUSPENDED || _state==__COMPLETING)
+                    {
+                        onComplete();
+                        return true;
+                    }
+                    
+                    _initial=false;
+                    _state=__HANDLING;
+                    return false; 
+
+                case __RESUMING:
+                    _initial=false;
+                    _state=__HANDLING;
+                    return false; 
+
+                case __COMPLETING:
+                    _initial=false;
+                    _state=__COMPLETE;
+                    onComplete();
+                    return true;
+
+                case __SUSPENDED:
+                case __UNSUSPENDING:
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expire()
+    {
+        // just like resume, except don't set _resumed=true;
+
+        synchronized (this)
+        {
+            _timeout=true;
+        }
+        
+        onTimeout();
+        
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __HANDLING:
+                    return;
+                    
+                case __SUSPENDING:
+                    _timeout=true;
+                    _state=__RESUMING;
+                    fauxResume();
+                    return;
+                    
+                case __RESUMING:
+                    return;
+                    
+                case __COMPLETING:
+                    return;
+                    
+                case __SUSPENDED:
+                    _timeout=true;
+                    _state=__UNSUSPENDING;
+                    break;
+                    
+                case __UNSUSPENDING:
+                    _timeout=true;
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    private void fauxSuspend()
+    {
+        long expire_at = System.currentTimeMillis()+_timeoutMs;
+        long wait=_timeoutMs;
+        while (_timeoutMs>0 && wait>0)
+        {
+            try
+            {
+                this.wait(wait);
+            }
+            catch (InterruptedException e)
+            {
+                break;
+            }
+            wait=expire_at-System.currentTimeMillis();
+        }
+
+        if (_timeoutMs>0 && wait<=0)
+            expire();
+    }
+    
+    private void fauxResume()
+    {
+        _timeoutMs=0;
+        this.notifyAll();
+    }
+    
+    @Override
+    public String toString()
+    {
+        return getStatusString();
+    }
+    
+    String getStatusString()
+    {
+        synchronized (this)
+        {
+            return
+            ((_state==__HANDLING)?"HANDLING":
+                    (_state==__SUSPENDING)?"SUSPENDING":
+                        (_state==__SUSPENDED)?"SUSPENDED":
+                            (_state==__RESUMING)?"RESUMING":
+                                (_state==__UNSUSPENDING)?"UNSUSPENDING":
+                                    (_state==__COMPLETING)?"COMPLETING":
+                                    ("???"+_state))+
+            (_initial?",initial":"")+
+            (_resumed?",resumed":"")+
+            (_timeout?",timeout":"");
+        }
+    }
+
+    
+    public void addContinuationListener(ContinuationListener listener)
+    {
+        if (_listeners==null)
+            _listeners=new ArrayList<ContinuationListener>();
+        _listeners.add(listener);
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _request.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _request.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _request.setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
+     */
+    public void undispatch()
+    {
+        if (isSuspended())
+        {
+            if (ContinuationFilter.__debug)
+                throw new ContinuationThrowable();
+            throw __exception;
+        }
+        throw new IllegalStateException("!suspended");
+        
+    }
+}
diff --git a/src/java/org/eclipse/jetty/continuation/Jetty6Continuation.java b/src/java/org/eclipse/jetty/continuation/Jetty6Continuation.java
new file mode 100644
index 0000000..3ed177d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/Jetty6Continuation.java
@@ -0,0 +1,267 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+
+import org.mortbay.log.Log;
+import org.mortbay.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * This implementation of Continuation is used by {@link ContinuationSupport}
+ * when it detects that the application is deployed in a jetty-6 server.
+ * This continuation requires the {@link ContinuationFilter} to be deployed.
+ */
+public class Jetty6Continuation implements ContinuationFilter.FilteredContinuation
+{
+    private static final Logger LOG = Log.getLogger(Jetty6Continuation.class.getName());
+
+    // Exception reused for all continuations
+    // Turn on debug in ContinuationFilter to see real stack trace.
+    private final static ContinuationThrowable __exception = new ContinuationThrowable();
+
+    private final ServletRequest _request;
+    private ServletResponse _response;
+    private final org.mortbay.util.ajax.Continuation _j6Continuation;
+
+    private Throwable _retry;
+    private int _timeout;
+    private boolean _initial=true;
+    private volatile boolean _completed=false;
+    private volatile boolean _resumed=false;
+    private volatile boolean _expired=false;
+    private boolean _responseWrapped=false;
+    private List<ContinuationListener> _listeners;
+
+    public Jetty6Continuation(ServletRequest request, org.mortbay.util.ajax.Continuation continuation)
+    {
+        if (!ContinuationFilter._initialized)
+        {
+            LOG.warn("!ContinuationFilter installed",null,null);
+            throw new IllegalStateException("!ContinuationFilter installed");
+        }
+        _request=request;
+        _j6Continuation=continuation;
+    }
+
+    public void addContinuationListener(final ContinuationListener listener)
+    {
+        if (_listeners==null)
+            _listeners=new ArrayList<ContinuationListener>();
+        _listeners.add(listener);
+    }
+
+    public void complete()
+    {
+        synchronized(this)
+        {
+            if (_resumed)
+                throw new IllegalStateException();
+            _completed=true;
+            if (_j6Continuation.isPending())
+                _j6Continuation.resume();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _request.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _request.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _request.setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getServletResponse()
+    {
+        return _response;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isExpired()
+    {
+        return _expired;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isInitial()
+    {
+        return _initial;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResumed()
+    {
+        return _resumed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return _retry!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void resume()
+    {
+        synchronized(this)
+        {
+            if (_completed)
+                throw new IllegalStateException();
+            _resumed=true;
+            if (_j6Continuation.isPending())
+                _j6Continuation.resume();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeout(long timeoutMs)
+    {
+        _timeout=(timeoutMs>Integer.MAX_VALUE)?Integer.MAX_VALUE:(int)timeoutMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#suspend(javax.servlet.ServletResponse)
+     */
+    public void suspend(ServletResponse response)
+    {
+        try
+        {
+            _response=response;
+            _responseWrapped=_response instanceof ServletResponseWrapper;
+            _resumed=false;
+            _expired=false;
+            _completed=false;
+            _j6Continuation.suspend(_timeout);
+        }
+        catch(Throwable retry)
+        {
+            _retry=retry;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void suspend()
+    {
+        try
+        {
+            _response=null;
+            _responseWrapped=false;
+            _resumed=false;
+            _expired=false;
+            _completed=false;
+            _j6Continuation.suspend(_timeout);
+        }
+        catch(Throwable retry)
+        {
+            _retry=retry;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResponseWrapped()
+    {
+        return _responseWrapped;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
+     */
+    public void undispatch()
+    {
+        if (isSuspended())
+        {
+            if (ContinuationFilter.__debug)
+                throw new ContinuationThrowable();
+            throw __exception;
+        }
+        throw new IllegalStateException("!suspended");
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean enter(ServletResponse response)
+    {
+        _response=response;
+        _expired=!_j6Continuation.isResumed();
+
+        if (_initial)
+            return true;
+
+        _j6Continuation.reset();
+
+        if (_expired)
+        {
+            if (_listeners!=null)
+            {
+                for (ContinuationListener l: _listeners)
+                    l.onTimeout(this);
+            }
+        }
+
+        return !_completed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean exit()
+    {
+        _initial=false;
+
+        Throwable th=_retry;
+        _retry=null;
+        if (th instanceof Error)
+            throw (Error)th;
+        if (th instanceof RuntimeException)
+            throw (RuntimeException)th;
+
+        if (_listeners!=null)
+        {
+            for (ContinuationListener l: _listeners)
+                l.onComplete(this);
+        }
+
+        return true;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/continuation/Servlet3Continuation.java b/src/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
new file mode 100644
index 0000000..f07d3f5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/continuation/Servlet3Continuation.java
@@ -0,0 +1,256 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.continuation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * This implementation of Continuation is used by {@link ContinuationSupport}
+ * when it detects that the application has been deployed in a non-jetty Servlet 3
+ * server.
+ */
+public class Servlet3Continuation implements Continuation
+{
+    // Exception reused for all continuations
+    // Turn on debug in ContinuationFilter to see real stack trace.
+    private final static ContinuationThrowable __exception = new ContinuationThrowable();
+
+    private final ServletRequest _request;
+    private ServletResponse _response;
+    private AsyncContext _context;
+    private List<AsyncListener> _listeners=new ArrayList<AsyncListener>();
+    private volatile boolean _initial=true;
+    private volatile boolean _resumed=false;
+    private volatile boolean _expired=false;
+    private volatile boolean _responseWrapped=false;
+
+    private long _timeoutMs=-1;
+
+    /* ------------------------------------------------------------ */
+    public Servlet3Continuation(ServletRequest request)
+    {
+        _request=request;
+
+        _listeners.add(new AsyncListener()
+        {
+            public void onComplete(AsyncEvent event) throws IOException
+            {
+            }
+
+            public void onError(AsyncEvent event) throws IOException
+            {
+            }
+
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+                event.getAsyncContext().addListener(this);
+            }
+
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                _initial=false;
+                event.getAsyncContext().dispatch();
+            }
+        });
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addContinuationListener(final ContinuationListener listener)
+    {
+        AsyncListener wrapped = new AsyncListener()
+        {
+            public void onComplete(final AsyncEvent event) throws IOException
+            {
+                listener.onComplete(Servlet3Continuation.this);
+            }
+
+            public void onError(AsyncEvent event) throws IOException
+            {
+                listener.onComplete(Servlet3Continuation.this);
+            }
+
+            public void onStartAsync(AsyncEvent event) throws IOException
+            {
+                event.getAsyncContext().addListener(this);
+            }
+
+            public void onTimeout(AsyncEvent event) throws IOException
+            {
+                _expired=true;
+                listener.onTimeout(Servlet3Continuation.this);
+            }
+        };
+
+        if (_context!=null)
+            _context.addListener(wrapped);
+        else
+            _listeners.add(wrapped);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void complete()
+    {
+        AsyncContext context=_context;
+        if (context==null)
+            throw new IllegalStateException();
+        _context.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getServletResponse()
+    {
+        return _response;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isExpired()
+    {
+        return _expired;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isInitial()
+    {
+        // TODO - this is not perfect if non continuation API is used directly
+        return _initial&&_request.getDispatcherType()!=DispatcherType.ASYNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResumed()
+    {
+        return _resumed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return _request.isAsyncStarted();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void keepWrappers()
+    {
+        _responseWrapped=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void resume()
+    {
+        AsyncContext context=_context;
+        if (context==null)
+            throw new IllegalStateException();
+        _resumed=true;
+        _context.dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeout(long timeoutMs)
+    {
+        _timeoutMs=timeoutMs;
+        if (_context!=null)
+            _context.setTimeout(timeoutMs);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void suspend(ServletResponse response)
+    {
+        _response=response;
+        _responseWrapped=response instanceof ServletResponseWrapper;
+        _resumed=false;
+        _expired=false;
+        _context=_request.startAsync();
+        _context.setTimeout(_timeoutMs);
+
+        for (AsyncListener listener:_listeners)
+            _context.addListener(listener);
+        _listeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void suspend()
+    {
+        _resumed=false;
+        _expired=false;
+        _context=_request.startAsync();
+        _context.setTimeout(_timeoutMs);
+
+        for (AsyncListener listener:_listeners)
+            _context.addListener(listener);
+        _listeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResponseWrapped()
+    {
+        return _responseWrapped;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _request.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _request.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _request.setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
+     */
+    public void undispatch()
+    {
+        if (isSuspended())
+        {
+            if (ContinuationFilter.__debug)
+                throw new ContinuationThrowable();
+            throw __exception;
+        }
+        throw new IllegalStateException("!suspended");
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/AbstractGenerator.java b/src/java/org/eclipse/jetty/http/AbstractGenerator.java
new file mode 100644
index 0000000..8df3ebe
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/AbstractGenerator.java
@@ -0,0 +1,532 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Abstract Generator. Builds HTTP Messages.
+ *
+ * Currently this class uses a system parameter "jetty.direct.writers" to control
+ * two optional writer to byte conversions. buffer.writers=true will probably be
+ * faster, but will consume more memory.   This option is just for testing and tuning.
+ *
+ */
+public abstract class AbstractGenerator implements Generator
+{
+    private static final Logger LOG = Log.getLogger(AbstractGenerator.class);
+
+    // states
+    public final static int STATE_HEADER = 0;
+    public final static int STATE_CONTENT = 2;
+    public final static int STATE_FLUSHING = 3;
+    public final static int STATE_END = 4;
+
+    public static final byte[] NO_BYTES = {};
+
+    // data
+
+    protected final Buffers _buffers; // source of buffers
+    protected final EndPoint _endp;
+
+    protected int _state = STATE_HEADER;
+
+    protected int _status = 0;
+    protected int _version = HttpVersions.HTTP_1_1_ORDINAL;
+    protected  Buffer _reason;
+    protected  Buffer _method;
+    protected  String _uri;
+
+    protected long _contentWritten = 0;
+    protected long _contentLength = HttpTokens.UNKNOWN_CONTENT;
+    protected boolean _last = false;
+    protected boolean _head = false;
+    protected boolean _noContent = false;
+    protected Boolean _persistent = null;
+
+    protected Buffer _header; // Buffer for HTTP header (and maybe small _content)
+    protected Buffer _buffer; // Buffer for copy of passed _content
+    protected Buffer _content; // Buffer passed to addContent
+
+    protected Buffer _date;
+
+    private boolean _sendServerVersion;
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Constructor.
+     *
+     * @param buffers buffer pool
+     * @param io the end point
+     */
+    public AbstractGenerator(Buffers buffers, EndPoint io)
+    {
+        this._buffers = buffers;
+        this._endp = io;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public abstract boolean isRequest();
+
+    /* ------------------------------------------------------------------------------- */
+    public abstract boolean isResponse();
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isOpen()
+    {
+        return _endp.isOpen();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        _state = STATE_HEADER;
+        _status = 0;
+        _version = HttpVersions.HTTP_1_1_ORDINAL;
+        _reason = null;
+        _last = false;
+        _head = false;
+        _noContent=false;
+        _persistent = null;
+        _contentWritten = 0;
+        _contentLength = HttpTokens.UNKNOWN_CONTENT;
+        _date = null;
+
+        _content = null;
+        _method=null;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void returnBuffers()
+    {
+        if (_buffer!=null && _buffer.length()==0)
+        {
+            _buffers.returnBuffer(_buffer);
+            _buffer=null;
+        }
+
+        if (_header!=null && _header.length()==0)
+        {
+            _buffers.returnBuffer(_header);
+            _header=null;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void resetBuffer()
+    {
+        if(_state>=STATE_FLUSHING)
+            throw new IllegalStateException("Flushed");
+
+        _last = false;
+        _persistent=null;
+        _contentWritten = 0;
+        _contentLength = HttpTokens.UNKNOWN_CONTENT;
+        _content=null;
+        if (_buffer!=null)
+            _buffer.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the contentBufferSize.
+     */
+    public int getContentBufferSize()
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+        return _buffer.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contentBufferSize The contentBufferSize to set.
+     */
+    public void increaseContentBufferSize(int contentBufferSize)
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+        if (contentBufferSize > _buffer.capacity())
+        {
+            Buffer nb = _buffers.getBuffer(contentBufferSize);
+            nb.put(_buffer);
+            _buffers.returnBuffer(_buffer);
+            _buffer = nb;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getUncheckedBuffer()
+    {
+        return _buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getSendServerVersion ()
+    {
+        return _sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        _sendServerVersion = sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isState(int state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        return _state == STATE_END;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _state == STATE_HEADER && _method==null && _status==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCommitted()
+    {
+        return _state != STATE_HEADER;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the head.
+     */
+    public boolean isHead()
+    {
+        return _head;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setContentLength(long value)
+    {
+        if (value<0)
+            _contentLength=HttpTokens.UNKNOWN_CONTENT;
+        else
+            _contentLength=value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param head The head to set.
+     */
+    public void setHead(boolean head)
+    {
+        _head = head;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return <code>false</code> if the connection should be closed after a request has been read,
+     * <code>true</code> if it should be used for additional requests.
+     */
+    public boolean isPersistent()
+    {
+        return _persistent!=null
+        ?_persistent.booleanValue()
+        :(isRequest()?true:_version>HttpVersions.HTTP_1_0_ORDINAL);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent=persistent;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param version The version of the client the response is being sent to (NB. Not the version
+     *            in the response, which is the version of the server).
+     */
+    public void setVersion(int version)
+    {
+        if (_state != STATE_HEADER)
+            throw new IllegalStateException("STATE!=START "+_state);
+        _version = version;
+        if (_version==HttpVersions.HTTP_0_9_ORDINAL && _method!=null)
+            _noContent=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getVersion()
+    {
+        return _version;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.Generator#setDate(org.eclipse.jetty.io.Buffer)
+     */
+    public void setDate(Buffer timeStampBuffer)
+    {
+        _date=timeStampBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setRequest(String method, String uri)
+    {
+        if (method==null || HttpMethods.GET.equals(method) )
+            _method=HttpMethods.GET_BUFFER;
+        else
+            _method=HttpMethods.CACHE.lookup(method);
+        _uri=uri;
+        if (_version==HttpVersions.HTTP_0_9_ORDINAL)
+            _noContent=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param status The status code to send.
+     * @param reason the status message to send.
+     */
+    public void setResponse(int status, String reason)
+    {
+        if (_state != STATE_HEADER) throw new IllegalStateException("STATE!=START");
+        _method=null;
+        _status = status;
+        if (reason!=null)
+        {
+            int len=reason.length();
+
+            // TODO don't hard code
+            if (len>1024)
+                len=1024;
+            _reason=new ByteArrayBuffer(len);
+            for (int i=0;i<len;i++)
+            {
+                char ch = reason.charAt(i);
+                if (ch!='\r'&&ch!='\n')
+                    _reason.put((byte)ch);
+                else
+                    _reason.put((byte)' ');
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Prepare buffer for unchecked writes.
+     * Prepare the generator buffer to receive unchecked writes
+     * @return the available space in the buffer.
+     * @throws IOException
+     */
+    public abstract int prepareUncheckedAddContent() throws IOException;
+
+    /* ------------------------------------------------------------ */
+    void uncheckedAddContent(int b)
+    {
+        _buffer.put((byte)b);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void completeUncheckedAddContent()
+    {
+        if (_noContent)
+        {
+            if(_buffer!=null)
+                _buffer.clear();
+        }
+        else
+        {
+            _contentWritten+=_buffer.length();
+            if (_head)
+                _buffer.clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isBufferFull()
+    {
+        if (_buffer != null && _buffer.space()==0)
+        {
+            if (_buffer.length()==0 && !_buffer.isImmutable())
+                _buffer.compact();
+            return _buffer.space()==0;
+        }
+
+        return _content!=null && _content.length()>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _contentWritten>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isAllContentWritten()
+    {
+        return _contentLength>=0 && _contentWritten>=_contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    public abstract void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Complete the message.
+     *
+     * @throws IOException
+     */
+    public void complete() throws IOException
+    {
+        if (_state == STATE_HEADER)
+        {
+            throw new IllegalStateException("State==HEADER");
+        }
+
+        if (_contentLength >= 0 && _contentLength != _contentWritten && !_head)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("ContentLength written=="+_contentWritten+" != contentLength=="+_contentLength);
+            _persistent = false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public abstract int flushBuffer() throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    public void flush(long maxIdleTime) throws IOException
+    {
+        // block until everything is flushed
+        long now=System.currentTimeMillis();
+        long end=now+maxIdleTime;
+        Buffer content = _content;
+        Buffer buffer = _buffer;
+        if (content!=null && content.length()>0 || buffer!=null && buffer.length()>0 || isBufferFull())
+        {
+            flushBuffer();
+
+            while (now<end && (content!=null && content.length()>0 ||buffer!=null && buffer.length()>0) && _endp.isOpen()&& !_endp.isOutputShutdown())
+            {
+                blockForOutput(end-now);
+                now=System.currentTimeMillis();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Utility method to send an error response. If the builder is not committed, this call is
+     * equivalent to a setResponse, addContent and complete call.
+     *
+     * @param code The error code
+     * @param reason The error reason
+     * @param content Contents of the error page
+     * @param close True if the connection should be closed
+     * @throws IOException if there is a problem flushing the response
+     */
+    public void sendError(int code, String reason, String content, boolean close) throws IOException
+    {
+        if (close)
+            _persistent=false;
+        if (isCommitted())
+        {
+            LOG.debug("sendError on committed: {} {}",code,reason);
+        }
+        else
+        {
+            LOG.debug("sendError: {} {}",code,reason);
+            setResponse(code, reason);
+            if (content != null)
+            {
+                completeHeader(null, false);
+                addContent(new View(new ByteArrayBuffer(content)), Generator.LAST);
+            }
+            else if (code>=400)
+            {
+                completeHeader(null, false);
+                addContent(new View(new ByteArrayBuffer("Error: "+(reason==null?(""+code):reason))), Generator.LAST);
+            }
+            else
+            {
+                completeHeader(null, true);
+            }
+            complete();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the contentWritten.
+     */
+    public long getContentWritten()
+    {
+        return _contentWritten;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    public void  blockForOutput(long maxIdleTime) throws IOException
+    {
+        if (_endp.isBlocking())
+        {
+            try
+            {
+                flushBuffer();
+            }
+            catch(IOException e)
+            {
+                _endp.close();
+                throw e;
+            }
+        }
+        else
+        {
+            if (!_endp.blockWritable(maxIdleTime))
+            {
+                _endp.close();
+                throw new EofException("timeout");
+            }
+
+            flushBuffer();
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/http/EncodedHttpURI.java b/src/java/org/eclipse/jetty/http/EncodedHttpURI.java
new file mode 100644
index 0000000..1fd22d3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/EncodedHttpURI.java
@@ -0,0 +1,183 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuffer;
+
+public class EncodedHttpURI extends HttpURI
+{
+    private final String _encoding;
+    
+    public EncodedHttpURI(String encoding)
+    {
+        super();
+        _encoding = encoding;
+    }
+    
+    
+    @Override
+    public String getScheme()
+    {
+        if (_scheme==_authority)
+            return null;
+        int l=_authority-_scheme;
+        if (l==5 && 
+            _raw[_scheme]=='h' && 
+            _raw[_scheme+1]=='t' && 
+            _raw[_scheme+2]=='t' && 
+            _raw[_scheme+3]=='p' )
+            return HttpSchemes.HTTP;
+        if (l==6 && 
+            _raw[_scheme]=='h' && 
+            _raw[_scheme+1]=='t' && 
+            _raw[_scheme+2]=='t' && 
+            _raw[_scheme+3]=='p' && 
+            _raw[_scheme+4]=='s' )
+            return HttpSchemes.HTTPS;
+        
+        return StringUtil.toString(_raw,_scheme,_authority-_scheme-1,_encoding);
+    }
+    
+    @Override
+    public String getAuthority()
+    {
+        if (_authority==_path)
+            return null;
+        return StringUtil.toString(_raw,_authority,_path-_authority,_encoding);
+    }
+    
+    @Override
+    public String getHost()
+    {
+        if (_host==_port)
+            return null;
+        return StringUtil.toString(_raw,_host,_port-_host,_encoding);
+    }
+    
+    @Override
+    public int getPort()
+    {
+        if (_port==_path)
+            return -1;
+        return TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+    }
+    
+    @Override
+    public String getPath()
+    {
+        if (_path==_param)
+            return null;
+        return StringUtil.toString(_raw,_path,_param-_path,_encoding);
+    }
+    
+    @Override
+    public String getDecodedPath()
+    {
+        if (_path==_param)
+            return null;
+        return URIUtil.decodePath(_raw,_path,_param-_path);
+    }
+    
+    @Override
+    public String getPathAndParam()
+    {
+        if (_path==_query)
+            return null;
+        return StringUtil.toString(_raw,_path,_query-_path,_encoding);
+    }
+    
+    @Override
+    public String getCompletePath()
+    {
+        if (_path==_end)
+            return null;
+        return StringUtil.toString(_raw,_path,_end-_path,_encoding);
+    }
+    
+    @Override
+    public String getParam()
+    {
+        if (_param==_query)
+            return null;
+        return StringUtil.toString(_raw,_param+1,_query-_param-1,_encoding);
+    }
+    
+    @Override
+    public String getQuery()
+    {
+        if (_query==_fragment)
+            return null;
+        return StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding);
+    }
+    
+    @Override
+    public boolean hasQuery()
+    {
+        return (_fragment>_query);
+    }
+    
+    @Override
+    public String getFragment()
+    {
+        if (_fragment==_end)
+            return null;
+        return StringUtil.toString(_raw,_fragment+1,_end-_fragment-1,_encoding);
+    }
+
+    @Override
+    public void decodeQueryTo(MultiMap parameters) 
+    {
+        if (_query==_fragment)
+            return;
+        UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,_encoding),parameters,_encoding);
+    }
+
+    @Override
+    public void decodeQueryTo(MultiMap parameters, String encoding) 
+        throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+       
+        if (encoding==null)
+            encoding=_encoding;
+        UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
+    }
+    
+    @Override
+    public String toString()
+    {
+        if (_rawString==null)
+            _rawString= StringUtil.toString(_raw,_scheme,_end-_scheme,_encoding);
+        return _rawString;
+    }
+    
+    public void writeTo(Utf8StringBuffer buf)
+    {
+        buf.getStringBuffer().append(toString());
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/Generator.java b/src/java/org/eclipse/jetty/http/Generator.java
new file mode 100644
index 0000000..4e91434
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/Generator.java
@@ -0,0 +1,96 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+
+public interface Generator
+{
+    public static final boolean LAST=true;
+    public static final boolean MORE=false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add content.
+     * 
+     * @param content
+     * @param last
+     * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
+     * @throws IllegalStateException If the request is not expecting any more content,
+     *   or if the buffers are full and cannot be flushed.
+     * @throws IOException if there is a problem flushing the buffers.
+     */
+    void addContent(Buffer content, boolean last) throws IOException;
+
+    void complete() throws IOException;
+
+    void completeHeader(HttpFields responseFields, boolean last) throws IOException;
+
+    int flushBuffer() throws IOException;
+
+    int getContentBufferSize();
+
+    long getContentWritten();
+
+    boolean isWritten();
+    
+    boolean isAllContentWritten();
+
+    void increaseContentBufferSize(int size);
+    
+    boolean isBufferFull();
+
+    boolean isCommitted();
+
+    boolean isComplete();
+
+    boolean isPersistent();
+
+    void reset();
+
+    void resetBuffer();
+    
+    void returnBuffers();
+
+    void sendError(int code, String reason, String content, boolean close) throws IOException;
+    
+    void setHead(boolean head);
+
+    void setRequest(String method, String uri);
+
+    void setResponse(int status, String reason);
+
+
+    void setSendServerVersion(boolean sendServerVersion);
+ 
+    void setVersion(int version);
+
+    boolean isIdle();
+
+    void setContentLength(long length);
+    
+    void setPersistent(boolean persistent);
+
+    void setDate(Buffer timeStampBuffer);
+    
+
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpBuffers.java b/src/java/org/eclipse/jetty/http/HttpBuffers.java
new file mode 100644
index 0000000..58032bc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpBuffers.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.BuffersFactory;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/* ------------------------------------------------------------ */
+/** Abstract Buffer pool.
+ */
+public interface HttpBuffers
+{
+    /**
+     * @return the requestBufferSize
+     */
+    public int getRequestBufferSize();
+    
+    /**
+     * @param requestBufferSize the requestBufferSize to set
+     */
+    public void setRequestBufferSize(int requestBufferSize);
+
+    /**
+     * @return the requestHeaderSize
+     */
+    public int getRequestHeaderSize();
+
+    /**
+     * @param requestHeaderSize the requestHeaderSize to set
+     */
+    public void setRequestHeaderSize(int requestHeaderSize);
+
+    /**
+     * @return the responseBufferSize
+     */
+    public int getResponseBufferSize();
+
+    /**
+     * @param responseBufferSize the responseBufferSize to set
+     */
+    public void setResponseBufferSize(int responseBufferSize);
+
+    /**
+     * @return the responseHeaderSize
+     */
+    public int getResponseHeaderSize();
+
+    /**
+     * @param responseHeaderSize the responseHeaderSize to set
+     */
+    public void setResponseHeaderSize(int responseHeaderSize);
+
+    /**
+     * @return the requestBufferType
+     */
+    public Buffers.Type getRequestBufferType();
+
+    /**
+     * @return the requestHeaderType
+     */
+    public Buffers.Type getRequestHeaderType();
+
+    /**
+     * @return the responseBufferType
+     */
+    public Buffers.Type getResponseBufferType();
+
+    /**
+     * @return the responseHeaderType
+     */
+    public Buffers.Type getResponseHeaderType();
+
+    /**
+     * @param requestBuffers the requestBuffers to set
+     */
+    public void setRequestBuffers(Buffers requestBuffers);
+
+    /**
+     * @param responseBuffers the responseBuffers to set
+     */
+    public void setResponseBuffers(Buffers responseBuffers);
+
+    public Buffers getRequestBuffers();
+
+    public Buffers getResponseBuffers();
+
+    public void setMaxBuffers(int maxBuffers);
+
+    public int getMaxBuffers();
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpBuffersImpl.java b/src/java/org/eclipse/jetty/http/HttpBuffersImpl.java
new file mode 100644
index 0000000..6bf5ec3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpBuffersImpl.java
@@ -0,0 +1,238 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.BuffersFactory;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+/* ------------------------------------------------------------ */
+/** Abstract Buffer pool.
+ * simple unbounded pool of buffers for header, request and response sizes.
+ *
+ */
+public class HttpBuffersImpl extends AbstractLifeCycle implements HttpBuffers
+{
+    private int _requestBufferSize=16*1024;
+    private int _requestHeaderSize=6*1024;
+    private int _responseBufferSize=32*1024;
+    private int _responseHeaderSize=6*1024;
+    private int _maxBuffers=1024;
+    
+    private Buffers.Type _requestBufferType=Buffers.Type.BYTE_ARRAY;
+    private Buffers.Type _requestHeaderType=Buffers.Type.BYTE_ARRAY;
+    private Buffers.Type _responseBufferType=Buffers.Type.BYTE_ARRAY;
+    private Buffers.Type _responseHeaderType=Buffers.Type.BYTE_ARRAY;
+    
+    private Buffers _requestBuffers;
+    private Buffers _responseBuffers;
+    
+    
+    public HttpBuffersImpl()
+    {
+        super();
+    }
+    
+    /**
+     * @return the requestBufferSize
+     */
+    public int getRequestBufferSize()
+    {
+        return _requestBufferSize;
+    }
+    
+    /**
+     * @param requestBufferSize the requestBufferSize to set
+     */
+    public void setRequestBufferSize(int requestBufferSize)
+    {
+        _requestBufferSize = requestBufferSize;
+    }
+
+    /**
+     * @return the requestHeaderSize
+     */
+    public int getRequestHeaderSize()
+    {
+        return _requestHeaderSize;
+    }
+
+    /**
+     * @param requestHeaderSize the requestHeaderSize to set
+     */
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _requestHeaderSize = requestHeaderSize;
+    }
+
+    /**
+     * @return the responseBufferSize
+     */
+    public int getResponseBufferSize()
+    {
+        return _responseBufferSize;
+    }
+
+    /**
+     * @param responseBufferSize the responseBufferSize to set
+     */
+    public void setResponseBufferSize(int responseBufferSize)
+    {
+        _responseBufferSize = responseBufferSize;
+    }
+
+    /**
+     * @return the responseHeaderSize
+     */
+    public int getResponseHeaderSize()
+    {
+        return _responseHeaderSize;
+    }
+
+    /**
+     * @param responseHeaderSize the responseHeaderSize to set
+     */
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _responseHeaderSize = responseHeaderSize;
+    }
+
+    /**
+     * @return the requestBufferType
+     */
+    public Buffers.Type getRequestBufferType()
+    {
+        return _requestBufferType;
+    }
+
+    /**
+     * @param requestBufferType the requestBufferType to set
+     */
+    public void setRequestBufferType(Buffers.Type requestBufferType)
+    {
+        _requestBufferType = requestBufferType;
+    }
+
+    /**
+     * @return the requestHeaderType
+     */
+    public Buffers.Type getRequestHeaderType()
+    {
+        return _requestHeaderType;
+    }
+
+    /**
+     * @param requestHeaderType the requestHeaderType to set
+     */
+    public void setRequestHeaderType(Buffers.Type requestHeaderType)
+    {
+        _requestHeaderType = requestHeaderType;
+    }
+
+    /**
+     * @return the responseBufferType
+     */
+    public Buffers.Type getResponseBufferType()
+    {
+        return _responseBufferType;
+    }
+
+    /**
+     * @param responseBufferType the responseBufferType to set
+     */
+    public void setResponseBufferType(Buffers.Type responseBufferType)
+    {
+        _responseBufferType = responseBufferType;
+    }
+
+    /**
+     * @return the responseHeaderType
+     */
+    public Buffers.Type getResponseHeaderType()
+    {
+        return _responseHeaderType;
+    }
+
+    /**
+     * @param responseHeaderType the responseHeaderType to set
+     */
+    public void setResponseHeaderType(Buffers.Type responseHeaderType)
+    {
+        _responseHeaderType = responseHeaderType;
+    }
+
+    /**
+     * @param requestBuffers the requestBuffers to set
+     */
+    public void setRequestBuffers(Buffers requestBuffers)
+    {
+        _requestBuffers = requestBuffers;
+    }
+
+    /**
+     * @param responseBuffers the responseBuffers to set
+     */
+    public void setResponseBuffers(Buffers responseBuffers)
+    {
+        _responseBuffers = responseBuffers;
+    }
+
+    @Override
+    protected void doStart()
+        throws Exception
+    {
+        _requestBuffers=BuffersFactory.newBuffers(_requestHeaderType,_requestHeaderSize,_requestBufferType,_requestBufferSize,_requestBufferType,getMaxBuffers());
+        _responseBuffers=BuffersFactory.newBuffers(_responseHeaderType,_responseHeaderSize,_responseBufferType,_responseBufferSize,_responseBufferType,getMaxBuffers());
+        super.doStart();
+    }
+    
+    @Override
+    protected void doStop()
+        throws Exception
+    {
+        _requestBuffers=null;
+        _responseBuffers=null;
+    }
+
+    public Buffers getRequestBuffers()
+    {
+        return _requestBuffers;
+    }
+    
+
+    public Buffers getResponseBuffers()
+    {
+        return _responseBuffers;
+    }
+
+    public void setMaxBuffers(int maxBuffers)
+    {
+        _maxBuffers = maxBuffers;
+    }
+
+    public int getMaxBuffers()
+    {
+        return _maxBuffers;
+    }
+    
+    public String toString()
+    {
+        return _requestBuffers+"/"+_responseBuffers;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpContent.java b/src/java/org/eclipse/jetty/http/HttpContent.java
new file mode 100644
index 0000000..5961591
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpContent.java
@@ -0,0 +1,167 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** HttpContent.
+ * 
+ *
+ */
+public interface HttpContent
+{
+    Buffer getContentType();
+    Buffer getLastModified();
+    Buffer getIndirectBuffer();
+    Buffer getDirectBuffer();
+    Buffer getETag();
+    Resource getResource();
+    long getContentLength();
+    InputStream getInputStream() throws IOException;
+    void release();
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class ResourceAsHttpContent implements HttpContent
+    {
+        private static final Logger LOG = Log.getLogger(ResourceAsHttpContent.class);
+        
+        final Resource _resource;
+        final Buffer _mimeType;
+        final int _maxBuffer;
+        final Buffer _etag;
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType)
+        {
+            this(resource,mimeType,-1,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer)
+        {
+            this(resource,mimeType,maxBuffer,false);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, boolean etag)
+        {
+            this(resource,mimeType,-1,etag);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ResourceAsHttpContent(final Resource resource, final Buffer mimeType, int maxBuffer, boolean etag)
+        {
+            _resource=resource;
+            _mimeType=mimeType;
+            _maxBuffer=maxBuffer;
+            _etag=etag?new ByteArrayBuffer(resource.getWeakETag()):null;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getContentType()
+        {
+            return _mimeType;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getLastModified()
+        {
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getDirectBuffer()
+        {
+            return null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Buffer getETag()
+        {
+            return _etag;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getIndirectBuffer()
+        {
+            InputStream inputStream = null;
+            try
+            {
+                if (_resource.length() <= 0 || _maxBuffer < _resource.length())
+                    return null;
+                ByteArrayBuffer buffer = new ByteArrayBuffer((int)_resource.length());
+                inputStream = _resource.getInputStream();
+                buffer.readFrom(inputStream,(int)_resource.length());
+                return buffer;
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            finally
+            {
+                if (inputStream != null)
+                {
+                    try
+                    {
+                        inputStream.close();
+                    }
+                    catch (IOException e)
+                    {
+                        LOG.warn("Couldn't close inputStream. Possible file handle leak",e);
+                    }
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public long getContentLength()
+        {
+            return _resource.length();
+        }
+
+        /* ------------------------------------------------------------ */
+        public InputStream getInputStream() throws IOException
+        {
+            return _resource.getInputStream();
+        }
+
+        /* ------------------------------------------------------------ */
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void release()
+        {
+            _resource.release();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpCookie.java b/src/java/org/eclipse/jetty/http/HttpCookie.java
new file mode 100644
index 0000000..02da647
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpCookie.java
@@ -0,0 +1,191 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+public class HttpCookie
+{
+    private final String _name;        
+    private final String _value;     
+    private final String _comment;                               
+    private final String _domain;    
+    private final int _maxAge;  
+    private final String _path;       
+    private final boolean _secure;   
+    private final int _version;   
+    private final boolean _httpOnly;
+
+    /* ------------------------------------------------------------ */
+    public HttpCookie(String name, String value)
+    {
+        super();
+        _name = name;
+        _value = value;
+        _comment = null;
+        _domain = null;
+        _httpOnly = false;
+        _maxAge = -1;
+        _path = null;
+        _secure = false;
+        _version = 0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public HttpCookie(String name, String value, String domain, String path)
+    {
+        super();
+        _name = name;
+        _value = value;
+        _comment = null;
+        _domain = domain;
+        _httpOnly = false;
+        _maxAge = -1;
+        _path = path;
+        _secure = false;
+        _version = 0;
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    public HttpCookie(String name, String value, int maxAge)
+    {
+        super();
+        _name = name;
+        _value = value;
+        _comment = null;
+        _domain = null;
+        _httpOnly = false;
+        _maxAge = maxAge;
+        _path = null;
+        _secure = false;
+        _version = 0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure)
+    {
+        super();
+        _comment = null;
+        _domain = domain;
+        _httpOnly = httpOnly;
+        _maxAge = maxAge;
+        _name = name;
+        _path = path;
+        _secure = secure;
+        _value = value;
+        _version = 0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public HttpCookie(String name, String value, String domain, String path, int maxAge, boolean httpOnly, boolean secure, String comment, int version)
+    {
+        super();
+        _comment = comment;
+        _domain = domain;
+        _httpOnly = httpOnly;
+        _maxAge = maxAge;
+        _name = name;
+        _path = path;
+        _secure = secure;
+        _value = value;
+        _version = version;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the name.
+     * @return the name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the value.
+     * @return the value
+     */
+    public String getValue()
+    {
+        return _value;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the comment.
+     * @return the comment
+     */
+    public String getComment()
+    {
+        return _comment;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the domain.
+     * @return the domain
+     */
+    public String getDomain()
+    {
+        return _domain;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the maxAge.
+     * @return the maxAge
+     */
+    public int getMaxAge()
+    {
+        return _maxAge;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the path.
+     * @return the path
+     */
+    public String getPath()
+    {
+        return _path;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the secure.
+     * @return the secure
+     */
+    public boolean isSecure()
+    {
+        return _secure;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the version.
+     * @return the version
+     */
+    public int getVersion()
+    {
+        return _version;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the isHttpOnly.
+     * @return the isHttpOnly
+     */
+    public boolean isHttpOnly()
+    {
+        return _httpOnly;
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpException.java b/src/java/org/eclipse/jetty/http/HttpException.java
new file mode 100644
index 0000000..4d01cda
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpException.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+public class HttpException extends IOException
+{
+    int _status;
+    String _reason;
+
+    /* ------------------------------------------------------------ */
+    public HttpException(int status)
+    {
+        _status=status;
+        _reason=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpException(int status,String reason)
+    {
+        _status=status;
+        _reason=reason;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpException(int status,String reason, Throwable rootCause)
+    {
+        _status=status;
+        _reason=reason;
+        initCause(rootCause);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the reason.
+     */
+    public String getReason()
+    {
+        return _reason;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param reason The reason to set.
+     */
+    public void setReason(String reason)
+    {
+        _reason = reason;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the status.
+     */
+    public int getStatus()
+    {
+        return _status;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param status The status to set.
+     */
+    public void setStatus(int status)
+    {
+        _status = status;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return ("HttpException("+_status+","+_reason+","+super.getCause()+")");
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpFields.java b/src/java/org/eclipse/jetty/http/HttpFields.java
new file mode 100644
index 0000000..d485956
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpFields.java
@@ -0,0 +1,1406 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.BufferDateCache;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HTTP Fields. A collection of HTTP header and or Trailer fields. 
+ * 
+ * <p>This class is not synchronized as it is expected that modifications will only be performed by a
+ * single thread.
+ * 
+ * 
+ */
+public class HttpFields
+{
+    private static final Logger LOG = Log.getLogger(HttpFields.class);
+    
+    /* ------------------------------------------------------------ */
+    public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
+    public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
+    public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+
+    /* -------------------------------------------------------------- */
+    static
+    {
+        __GMT.setID("GMT");
+        __dateCache.setTimeZone(__GMT);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public final static String __separators = ", \t";
+
+    /* ------------------------------------------------------------ */
+    private static final String[] DAYS =
+    { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+    private static final String[] MONTHS =
+    { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
+
+    
+    /* ------------------------------------------------------------ */
+    private static class DateGenerator
+    {
+        private final StringBuilder buf = new StringBuilder(32);
+        private final GregorianCalendar gc = new GregorianCalendar(__GMT);
+        
+        /**
+         * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
+         */
+        public String formatDate(long date)
+        {
+            buf.setLength(0);
+            gc.setTimeInMillis(date);
+            
+            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+            int month = gc.get(Calendar.MONTH);
+            int year = gc.get(Calendar.YEAR);
+            int century = year / 100;
+            year = year % 100;
+            
+            int hours = gc.get(Calendar.HOUR_OF_DAY);
+            int minutes = gc.get(Calendar.MINUTE);
+            int seconds = gc.get(Calendar.SECOND);
+
+            buf.append(DAYS[day_of_week]);
+            buf.append(',');
+            buf.append(' ');
+            StringUtil.append2digits(buf, day_of_month);
+
+            buf.append(' ');
+            buf.append(MONTHS[month]);
+            buf.append(' ');
+            StringUtil.append2digits(buf, century);
+            StringUtil.append2digits(buf, year);
+            
+            buf.append(' ');
+            StringUtil.append2digits(buf, hours);
+            buf.append(':');
+            StringUtil.append2digits(buf, minutes);
+            buf.append(':');
+            StringUtil.append2digits(buf, seconds);
+            buf.append(" GMT");
+            return buf.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies
+         */
+        public void formatCookieDate(StringBuilder buf, long date)
+        {
+            gc.setTimeInMillis(date);
+            
+            int day_of_week = gc.get(Calendar.DAY_OF_WEEK);
+            int day_of_month = gc.get(Calendar.DAY_OF_MONTH);
+            int month = gc.get(Calendar.MONTH);
+            int year = gc.get(Calendar.YEAR);
+            year = year % 10000;
+
+            int epoch = (int) ((date / 1000) % (60 * 60 * 24));
+            int seconds = epoch % 60;
+            epoch = epoch / 60;
+            int minutes = epoch % 60;
+            int hours = epoch / 60;
+
+            buf.append(DAYS[day_of_week]);
+            buf.append(',');
+            buf.append(' ');
+            StringUtil.append2digits(buf, day_of_month);
+
+            buf.append('-');
+            buf.append(MONTHS[month]);
+            buf.append('-');
+            StringUtil.append2digits(buf, year/100);
+            StringUtil.append2digits(buf, year%100);
+            
+            buf.append(' ');
+            StringUtil.append2digits(buf, hours);
+            buf.append(':');
+            StringUtil.append2digits(buf, minutes);
+            buf.append(':');
+            StringUtil.append2digits(buf, seconds);
+            buf.append(" GMT");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>()
+    {
+        @Override
+        protected DateGenerator initialValue()
+        {
+            return new DateGenerator();
+        }
+    };
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 
+     */
+    public static String formatDate(long date)
+    {
+        return __dateGenerator.get().formatDate(date);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static void formatCookieDate(StringBuilder buf, long date)
+    {
+        __dateGenerator.get().formatCookieDate(buf,date);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies
+     */
+    public static String formatCookieDate(long date)
+    {
+        StringBuilder buf = new StringBuilder(28);
+        formatCookieDate(buf, date);
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    private final static String __dateReceiveFmt[] =
+    {   
+        "EEE, dd MMM yyyy HH:mm:ss zzz", 
+        "EEE, dd-MMM-yy HH:mm:ss",
+        "EEE MMM dd HH:mm:ss yyyy",
+
+        "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 
+        "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 
+        "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 
+        "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", 
+        "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz",  
+        "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 
+        "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss",
+    };
+
+    /* ------------------------------------------------------------ */
+    private static class DateParser
+    {
+        final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length];
+ 
+        long parse(final String dateVal)
+        {
+            for (int i = 0; i < _dateReceive.length; i++)
+            {
+                if (_dateReceive[i] == null)
+                {
+                    _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US);
+                    _dateReceive[i].setTimeZone(__GMT);
+                }
+
+                try
+                {
+                    Date date = (Date) _dateReceive[i].parseObject(dateVal);
+                    return date.getTime();
+                }
+                catch (java.lang.Exception e)
+                {
+                    // LOG.ignore(e);
+                }
+            }
+            
+            if (dateVal.endsWith(" GMT"))
+            {
+                final String val = dateVal.substring(0, dateVal.length() - 4);
+
+                for (int i = 0; i < _dateReceive.length; i++)
+                {
+                    try
+                    {
+                        Date date = (Date) _dateReceive[i].parseObject(val);
+                        return date.getTime();
+                    }
+                    catch (java.lang.Exception e)
+                    {
+                        // LOG.ignore(e);
+                    }
+                }
+            }    
+            return -1;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static long parseDate(String date)
+    {
+        return __dateParser.get().parse(date);
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>()
+    {
+        @Override
+        protected DateParser initialValue()
+        {
+            return new DateParser();
+        }
+    };
+
+    /* -------------------------------------------------------------- */
+    public final static String __01Jan1970=formatDate(0);
+    public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970);
+    public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim();
+
+    /* -------------------------------------------------------------- */
+    private final ArrayList<Field> _fields = new ArrayList<Field>(20);
+    private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public HttpFields()
+    {
+    }
+
+    // TODO externalize this cache so it can be configurable
+    private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>();
+    private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000);
+    
+    /* -------------------------------------------------------------- */
+    private Buffer convertValue(String value)
+    {
+        Buffer buffer = __cache.get(value);
+        if (buffer!=null)
+            return buffer;
+        
+        try
+        {   
+            buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1);
+            
+            if (__cacheSize>0)
+            {
+                if (__cache.size()>__cacheSize)
+                    __cache.clear();
+                Buffer b=__cache.putIfAbsent(value,buffer);
+                if (b!=null)
+                    buffer=b;
+            }
+            
+            return buffer;
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /**
+     * Get Collection of header names. 
+     */
+    public Collection<String> getFieldNamesCollection()
+    {
+        final List<String> list = new ArrayList<String>(_fields.size());
+
+	for (Field f : _fields)
+	{
+	    if (f!=null)
+	        list.add(BufferUtil.to8859_1_String(f._name));
+	}
+	return list;
+    }
+    
+    /* -------------------------------------------------------------- */
+    /**
+     * Get enumeration of header _names. Returns an enumeration of strings representing the header
+     * _names for this request.
+     */
+    public Enumeration<String> getFieldNames()
+    {
+        final Enumeration<?> buffers = Collections.enumeration(_names.keySet());
+        return new Enumeration<String>()
+        {
+            public String nextElement()
+            {
+                return buffers.nextElement().toString();
+            }
+            
+            public boolean hasMoreElements()
+            {
+                return buffers.hasMoreElements();
+            }
+        }; 
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return _fields.size();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a Field by index.
+     * @return A Field value or null if the Field value has not been set
+     * 
+     */
+    public Field getField(int i)
+    {
+        return _fields.get(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    private Field getField(String name)
+    {
+        return _names.get(HttpHeaders.CACHE.lookup(name));
+    }
+
+    /* ------------------------------------------------------------ */
+    private Field getField(Buffer name)
+    {
+        return _names.get(HttpHeaders.CACHE.lookup(name));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean containsKey(Buffer name)
+    {
+        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean containsKey(String name)
+    {
+        return _names.containsKey(HttpHeaders.CACHE.lookup(name));
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * @return the value of a field, or null if not found. For multiple fields of the same name,
+     *         only the first is returned.
+     * @param name the case-insensitive field name
+     */
+    public String getStringField(String name)
+    {
+        Field field = getField(name);
+        return field==null?null:field.getValue();
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * @return the value of a field, or null if not found. For multiple fields of the same name,
+     *         only the first is returned.
+     * @param name the case-insensitive field name
+     */
+    public String getStringField(Buffer name)
+    {
+        Field field = getField(name);
+        return field==null?null:field.getValue();
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * @return the value of a field, or null if not found. For multiple fields of the same name,
+     *         only the first is returned.
+     * @param name the case-insensitive field name
+     */
+    public Buffer get(Buffer name)
+    {
+        Field field = getField(name);
+        return field==null?null:field._value;
+    }
+
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get multi headers
+     * 
+     * @return Enumeration of the values, or null if no such header.
+     * @param name the case-insensitive field name
+     */
+    public Collection<String> getValuesCollection(String name)
+    {
+        Field field = getField(name);
+	if (field==null)
+	    return null;
+
+        final List<String> list = new ArrayList<String>();
+
+	while(field!=null)
+	{
+	    list.add(field.getValue());
+	    field=field._next;
+	}
+	return list;
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get multi headers
+     * 
+     * @return Enumeration of the values
+     * @param name the case-insensitive field name
+     */
+    public Enumeration<String> getValues(String name)
+    {
+        final Field field = getField(name);
+        if (field == null) 
+        {
+            List<String> empty=Collections.emptyList();
+            return Collections.enumeration(empty);
+        }
+
+        return new Enumeration<String>()
+        {
+            Field f = field;
+
+            public boolean hasMoreElements()
+            {
+                return f != null;
+            }
+
+            public String nextElement() throws NoSuchElementException
+            {
+                if (f == null) throw new NoSuchElementException();
+                Field n = f;
+                f = f._next;
+                return n.getValue();
+            }
+        };
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get multi headers
+     * 
+     * @return Enumeration of the value Strings
+     * @param name the case-insensitive field name
+     */
+    public Enumeration<String> getValues(Buffer name)
+    {
+        final Field field = getField(name);
+        if (field == null) 
+        {
+            List<String> empty=Collections.emptyList();
+            return Collections.enumeration(empty);
+        }
+
+        return new Enumeration<String>()
+        {
+            Field f = field;
+
+            public boolean hasMoreElements()
+            {
+                return f != null;
+            }
+
+            public String nextElement() throws NoSuchElementException
+            {
+                if (f == null) throw new NoSuchElementException();
+                Field n = f;
+                f = f._next;
+                return n.getValue();
+            }
+        };
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get multi field values with separator. The multiple values can be represented as separate
+     * headers of the same name, or by a single header using the separator(s), or a combination of
+     * both. Separators may be quoted.
+     * 
+     * @param name the case-insensitive field name
+     * @param separators String of separators.
+     * @return Enumeration of the values, or null if no such header.
+     */
+    public Enumeration<String> getValues(String name, final String separators)
+    {
+        final Enumeration<String> e = getValues(name);
+        if (e == null) 
+            return null;
+        return new Enumeration<String>()
+        {
+            QuotedStringTokenizer tok = null;
+
+            public boolean hasMoreElements()
+            {
+                if (tok != null && tok.hasMoreElements()) return true;
+                while (e.hasMoreElements())
+                {
+                    String value = e.nextElement();
+                    tok = new QuotedStringTokenizer(value, separators, false, false);
+                    if (tok.hasMoreElements()) return true;
+                }
+                tok = null;
+                return false;
+            }
+
+            public String nextElement() throws NoSuchElementException
+            {
+                if (!hasMoreElements()) throw new NoSuchElementException();
+                String next = (String) tok.nextElement();
+                if (next != null) next = next.trim();
+                return next;
+            }
+        };
+    }
+
+    
+    /* -------------------------------------------------------------- */
+    /**
+     * Set a field.
+     * 
+     * @param name the name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(String name, String value)
+    {
+        if (value==null)
+            remove(name);
+        else
+        {
+            Buffer n = HttpHeaders.CACHE.lookup(name);
+            Buffer v = convertValue(value);
+            put(n, v);
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Set a field.
+     * 
+     * @param name the name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(Buffer name, String value)
+    {
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        Buffer v = convertValue(value);
+        put(n, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Set a field.
+     * 
+     * @param name the name of the field
+     * @param value the value of the field. If null the field is cleared.
+     */
+    public void put(Buffer name, Buffer value)
+    {
+        remove(name);
+        if (value == null)
+            return;
+
+        if (!(name instanceof BufferCache.CachedBuffer)) 
+            name = HttpHeaders.CACHE.lookup(name);
+        if (!(value instanceof CachedBuffer))
+            value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer();
+        
+        // new value;
+        Field field = new Field(name, value);
+        _fields.add(field);
+        _names.put(name, field);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Set a field.
+     * 
+     * @param name the name of the field
+     * @param list the List value of the field. If null the field is cleared.
+     */
+    public void put(String name, List<?> list)
+    {
+        if (list == null || list.size() == 0)
+        {
+            remove(name);
+            return;
+        }
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+
+        Object v = list.get(0);
+        if (v != null)
+            put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
+        else
+            remove(n);
+
+        if (list.size() > 1)
+        {
+            java.util.Iterator<?> iter = list.iterator();
+            iter.next();
+            while (iter.hasNext())
+            {
+                v = iter.next();
+                if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString()));
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     * 
+     * @param name the name of the field
+     * @param value the value of the field.
+     * @exception IllegalArgumentException If the name is a single valued field and already has a
+     *                value.
+     */
+    public void add(String name, String value) throws IllegalArgumentException
+    {
+        if (value==null)
+            return;
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        Buffer v = convertValue(value);
+        add(n, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Add to or set a field. If the field is allowed to have multiple values, add will add multiple
+     * headers of the same name.
+     * 
+     * @param name the name of the field
+     * @param value the value of the field.
+     * @exception IllegalArgumentException If the name is a single valued field and already has a
+     *                value.
+     */
+    public void add(Buffer name, Buffer value) throws IllegalArgumentException
+    {   
+        if (value == null) throw new IllegalArgumentException("null value");
+
+        if (!(name instanceof CachedBuffer))
+            name = HttpHeaders.CACHE.lookup(name);
+        name=name.asImmutableBuffer();
+        
+        if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name)))
+            value= HttpHeaderValues.CACHE.lookup(value);
+        value=value.asImmutableBuffer();
+        
+        Field field = _names.get(name);
+        Field last = null;
+        while (field != null)
+        {
+            last = field;
+            field = field._next;
+        }
+
+        // create the field
+        field = new Field(name, value);
+        _fields.add(field);
+
+        // look for chain to add too
+        if (last != null)
+            last._next = field;
+        else
+            _names.put(name, field);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a field.
+     * 
+     * @param name
+     */
+    public void remove(String name)
+    {
+        remove(HttpHeaders.CACHE.lookup(name));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove a field.
+     * 
+     * @param name
+     */
+    public void remove(Buffer name)
+    {
+        if (!(name instanceof BufferCache.CachedBuffer)) 
+            name = HttpHeaders.CACHE.lookup(name);
+        Field field = _names.remove(name);
+        while (field != null)
+        {
+            _fields.remove(field);
+            field = field._next;
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+     * case of the field name is ignored.
+     * 
+     * @param name the case-insensitive field name
+     * @exception NumberFormatException If bad long found
+     */
+    public long getLongField(String name) throws NumberFormatException
+    {
+        Field field = getField(name);
+        return field==null?-1L:field.getLongValue();
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get a header as an long value. Returns the value of an integer field or -1 if not found. The
+     * case of the field name is ignored.
+     * 
+     * @param name the case-insensitive field name
+     * @exception NumberFormatException If bad long found
+     */
+    public long getLongField(Buffer name) throws NumberFormatException
+    {
+        Field field = getField(name);
+        return field==null?-1L:field.getLongValue();
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
+     * of the field name is ignored.
+     * 
+     * @param name the case-insensitive field name
+     */
+    public long getDateField(String name)
+    {
+        Field field = getField(name);
+        if (field == null) 
+            return -1;
+
+        String val = valueParameters(BufferUtil.to8859_1_String(field._value), null);
+        if (val == null) 
+            return -1;
+
+        final long date = __dateParser.get().parse(val);
+        if (date==-1)
+            throw new IllegalArgumentException("Cannot convert date: " + val);
+        return date;
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of an long field.
+     * 
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(Buffer name, long value)
+    {
+        Buffer v = BufferUtil.toBuffer(value);
+        put(name, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of an long field.
+     * 
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void putLongField(String name, long value)
+    {
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        Buffer v = BufferUtil.toBuffer(value);
+        put(n, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of an long field.
+     * 
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void addLongField(String name, long value)
+    {
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        Buffer v = BufferUtil.toBuffer(value);
+        add(n, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of an long field.
+     * 
+     * @param name the field name
+     * @param value the field long value
+     */
+    public void addLongField(Buffer name, long value)
+    {
+        Buffer v = BufferUtil.toBuffer(value);
+        add(name, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of a date field.
+     * 
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(Buffer name, long date)
+    {
+        String d=formatDate(date);
+        Buffer v = new ByteArrayBuffer(d);
+        put(name, v);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of a date field.
+     * 
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void putDateField(String name, long date)
+    {
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        putDateField(n,date);
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Sets the value of a date field.
+     * 
+     * @param name the field name
+     * @param date the field date value
+     */
+    public void addDateField(String name, long date)
+    {
+        String d=formatDate(date);
+        Buffer n = HttpHeaders.CACHE.lookup(name);
+        Buffer v = new ByteArrayBuffer(d);
+        add(n, v);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Format a set cookie value
+     * 
+     * @param cookie The cookie.
+     */
+    public void addSetCookie(HttpCookie cookie)
+    {
+        addSetCookie(
+                cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                cookie.getComment(),
+                cookie.isSecure(),
+                cookie.isHttpOnly(),
+                cookie.getVersion());
+    }
+
+    /**
+     * Format a set cookie value
+     * 
+     * @param name the name
+     * @param value the value
+     * @param domain the domain
+     * @param path the path
+     * @param maxAge the maximum age
+     * @param comment the comment (only present on versions > 0)
+     * @param isSecure true if secure cookie
+     * @param isHttpOnly true if for http only
+     * @param version version of cookie logic to use (0 == default behavior)
+     */
+    public void addSetCookie(
+            final String name, 
+            final String value, 
+            final String domain,
+            final String path, 
+            final long maxAge,
+            final String comment, 
+            final boolean isSecure,
+            final boolean isHttpOnly, 
+            int version)
+    {
+    	String delim=__COOKIE_DELIM;
+    	
+        // Check arguments
+        if (name == null || name.length() == 0) 
+            throw new IllegalArgumentException("Bad cookie name");
+
+        // Format value and params
+        StringBuilder buf = new StringBuilder(128);
+        String name_value_params;
+        QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
+        buf.append('=');
+        String start=buf.toString();
+        boolean hasDomain = false;
+        boolean hasPath = false;
+        
+        if (value != null && value.length() > 0)
+            QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);        
+
+        if (comment != null && comment.length() > 0)
+        {
+            buf.append(";Comment=");
+            QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim);
+        }
+
+        if (path != null && path.length() > 0)
+        {
+            hasPath = true;
+            buf.append(";Path=");
+            if (path.trim().startsWith("\""))
+                buf.append(path);
+            else
+                QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
+        }
+        if (domain != null && domain.length() > 0)
+        {
+            hasDomain = true;
+            buf.append(";Domain=");
+            QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim);
+        }
+
+        if (maxAge >= 0)
+        {
+            // Always add the expires param as some browsers still don't handle max-age
+            buf.append(";Expires=");
+            if (maxAge == 0)
+                buf.append(__01Jan1970_COOKIE);
+            else
+                formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
+
+            if (version >0)
+            {
+                buf.append(";Max-Age=");
+                buf.append(maxAge);
+            }
+        }
+
+        if (isSecure)
+            buf.append(";Secure");
+        if (isHttpOnly) 
+            buf.append(";HttpOnly");
+
+        name_value_params = buf.toString();
+        
+        // remove existing set-cookie of same name
+        Field field = getField(HttpHeaders.SET_COOKIE);
+        Field last=null;
+        while (field!=null)
+        {
+            String val = (field._value == null ? null : field._value.toString());
+            if (val!=null && val.startsWith(start))
+            {
+                //existing cookie has same name, does it also match domain and path?
+                if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
+                    ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
+                {
+                    _fields.remove(field);
+                    if (last==null)
+                        _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next);
+                    else
+                        last._next=field._next;
+                    break;
+                }
+            }
+            last=field;
+            field=field._next;
+        }
+
+        add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params));
+        
+        // Expire responses with set-cookie headers so they do not get cached.
+        put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER);
+    }
+
+    /* -------------------------------------------------------------- */
+    public void putTo(Buffer buffer) throws IOException
+    {
+        for (int i = 0; i < _fields.size(); i++)
+        {
+            Field field = _fields.get(i);
+            if (field != null) 
+                field.putTo(buffer);
+        }
+        BufferUtil.putCRLF(buffer);
+    }
+
+    /* -------------------------------------------------------------- */
+    public String toString()
+    {
+        try
+        {
+            StringBuffer buffer = new StringBuffer();
+            for (int i = 0; i < _fields.size(); i++)
+            {
+                Field field = (Field) _fields.get(i);
+                if (field != null)
+                {
+                    String tmp = field.getName();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append(": ");
+                    tmp = field.getValue();
+                    if (tmp != null) buffer.append(tmp);
+                    buffer.append("\r\n");
+                }
+            }
+            buffer.append("\r\n");
+            return buffer.toString();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+            return e.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Clear the header.
+     */
+    public void clear()
+    {
+        _fields.clear();
+        _names.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add fields from another HttpFields instance. Single valued fields are replaced, while all
+     * others are added.
+     * 
+     * @param fields
+     */
+    public void add(HttpFields fields)
+    {
+        if (fields == null) return;
+
+        Enumeration e = fields.getFieldNames();
+        while (e.hasMoreElements())
+        {
+            String name = (String) e.nextElement();
+            Enumeration values = fields.getValues(name);
+            while (values.hasMoreElements())
+                add(name, (String) values.nextElement());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get field value parameters. Some field values can have parameters. This method separates the
+     * value from the parameters and optionally populates a map with the parameters. For example:
+     * 
+     * <PRE>
+     * 
+     * FieldName : Value ; param1=val1 ; param2=val2
+     * 
+     * </PRE>
+     * 
+     * @param value The Field value, possibly with parameteres.
+     * @param parameters A map to populate with the parameters, or null
+     * @return The value.
+     */
+    public static String valueParameters(String value, Map<String,String> parameters)
+    {
+        if (value == null) return null;
+
+        int i = value.indexOf(';');
+        if (i < 0) return value;
+        if (parameters == null) return value.substring(0, i).trim();
+
+        StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true);
+        while (tok1.hasMoreTokens())
+        {
+            String token = tok1.nextToken();
+            StringTokenizer tok2 = new QuotedStringTokenizer(token, "= ");
+            if (tok2.hasMoreTokens())
+            {
+                String paramName = tok2.nextToken();
+                String paramVal = null;
+                if (tok2.hasMoreTokens()) paramVal = tok2.nextToken();
+                parameters.put(paramName, paramVal);
+            }
+        }
+
+        return value.substring(0, i).trim();
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final Float __one = new Float("1.0");
+    private static final Float __zero = new Float("0.0");
+    private static final StringMap __qualities = new StringMap();
+    static
+    {
+        __qualities.put(null, __one);
+        __qualities.put("1.0", __one);
+        __qualities.put("1", __one);
+        __qualities.put("0.9", new Float("0.9"));
+        __qualities.put("0.8", new Float("0.8"));
+        __qualities.put("0.7", new Float("0.7"));
+        __qualities.put("0.66", new Float("0.66"));
+        __qualities.put("0.6", new Float("0.6"));
+        __qualities.put("0.5", new Float("0.5"));
+        __qualities.put("0.4", new Float("0.4"));
+        __qualities.put("0.33", new Float("0.33"));
+        __qualities.put("0.3", new Float("0.3"));
+        __qualities.put("0.2", new Float("0.2"));
+        __qualities.put("0.1", new Float("0.1"));
+        __qualities.put("0", __zero);
+        __qualities.put("0.0", __zero);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Float getQuality(String value)
+    {
+        if (value == null) return __zero;
+
+        int qe = value.indexOf(";");
+        if (qe++ < 0 || qe == value.length()) return __one;
+
+        if (value.charAt(qe++) == 'q')
+        {
+            qe++;
+            Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe);
+            if (entry != null) return (Float) entry.getValue();
+        }
+
+        HashMap params = new HashMap(3);
+        valueParameters(value, params);
+        String qs = (String) params.get("q");
+        Float q = (Float) __qualities.get(qs);
+        if (q == null)
+        {
+            try
+            {
+                q = new Float(qs);
+            }
+            catch (Exception e)
+            {
+                q = __one;
+            }
+        }
+        return q;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * List values in quality order.
+     * 
+     * @param e Enumeration of values with quality parameters
+     * @return values in quality order.
+     */
+    public static List qualityList(Enumeration e)
+    {
+        if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST;
+
+        Object list = null;
+        Object qual = null;
+
+        // Assume list will be well ordered and just add nonzero
+        while (e.hasMoreElements())
+        {
+            String v = e.nextElement().toString();
+            Float q = getQuality(v);
+
+            if (q.floatValue() >= 0.001)
+            {
+                list = LazyList.add(list, v);
+                qual = LazyList.add(qual, q);
+            }
+        }
+
+        List vl = LazyList.getList(list, false);
+        if (vl.size() < 2) return vl;
+
+        List ql = LazyList.getList(qual, false);
+
+        // sort list with swaps
+        Float last = __zero;
+        for (int i = vl.size(); i-- > 0;)
+        {
+            Float q = (Float) ql.get(i);
+            if (last.compareTo(q) > 0)
+            {
+                Object tmp = vl.get(i);
+                vl.set(i, vl.get(i + 1));
+                vl.set(i + 1, tmp);
+                ql.set(i, ql.get(i + 1));
+                ql.set(i + 1, q);
+                last = __zero;
+                i = vl.size();
+                continue;
+            }
+            last = q;
+        }
+        ql.clear();
+        return vl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static final class Field
+    {
+        private Buffer _name;
+        private Buffer _value;
+        private Field _next;
+
+        /* ------------------------------------------------------------ */
+        private Field(Buffer name, Buffer value)
+        {
+            _name = name;
+            _value = value;
+            _next = null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public void putTo(Buffer buffer) throws IOException
+        {
+            int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1;
+            if (o>=0)
+                buffer.put(_name);
+            else
+            {
+                int s=_name.getIndex();
+                int e=_name.putIndex();
+                while (s<e)
+                {
+                    byte b=_name.peek(s++);
+                    switch(b)
+                    {
+                        case '\r':
+                        case '\n':
+                        case ':' :
+                            continue;
+                        default:
+                            buffer.put(b);
+                    }
+                }
+            }
+            
+            buffer.put((byte) ':');
+            buffer.put((byte) ' ');
+            
+            o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1;
+            if (o>=0)
+                buffer.put(_value);
+            else
+            {
+                int s=_value.getIndex();
+                int e=_value.putIndex();
+                while (s<e)
+                {
+                    byte b=_value.peek(s++);
+                    switch(b)
+                    {
+                        case '\r':
+                        case '\n':
+                            continue;
+                        default:
+                            buffer.put(b);
+                    }
+                }
+            }
+
+            BufferUtil.putCRLF(buffer);
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getName()
+        {
+            return BufferUtil.to8859_1_String(_name);
+        }
+
+        /* ------------------------------------------------------------ */
+        Buffer getNameBuffer()
+        {
+            return _name;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getNameOrdinal()
+        {
+            return HttpHeaders.CACHE.getOrdinal(_name);
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getValue()
+        {
+            return BufferUtil.to8859_1_String(_value);
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getValueBuffer()
+        {
+            return _value;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getValueOrdinal()
+        {
+            return HttpHeaderValues.CACHE.getOrdinal(_value);
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getIntValue()
+        {
+            return (int) getLongValue();
+        }
+
+        /* ------------------------------------------------------------ */
+        public long getLongValue()
+        {
+            return BufferUtil.toLong(_value);
+        }
+
+        /* ------------------------------------------------------------ */
+        public String toString()
+        {
+            return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]");
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpGenerator.java b/src/java/org/eclipse/jetty/http/HttpGenerator.java
new file mode 100644
index 0000000..3a234f7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpGenerator.java
@@ -0,0 +1,1092 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * HttpGenerator. Builds HTTP Messages.
+ *
+ *
+ *
+ */
+public class HttpGenerator extends AbstractGenerator
+{
+    private static final Logger LOG = Log.getLogger(HttpGenerator.class);
+
+    // Build cache of response lines for status
+    private static class Status
+    {
+        Buffer _reason;
+        Buffer _schemeCode;
+        Buffer _responseLine;
+    }
+    private static final Status[] __status = new Status[HttpStatus.MAX_CODE+1];
+    static
+    {
+        int versionLength=HttpVersions.HTTP_1_1_BUFFER.length();
+
+        for (int i=0;i<__status.length;i++)
+        {
+            HttpStatus.Code code = HttpStatus.getCode(i);
+            if (code==null)
+                continue;
+            String reason=code.getMessage();
+            byte[] bytes=new byte[versionLength+5+reason.length()+2];
+            HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength);
+            bytes[versionLength+0]=' ';
+            bytes[versionLength+1]=(byte)('0'+i/100);
+            bytes[versionLength+2]=(byte)('0'+(i%100)/10);
+            bytes[versionLength+3]=(byte)('0'+(i%10));
+            bytes[versionLength+4]=' ';
+            for (int j=0;j<reason.length();j++)
+                bytes[versionLength+5+j]=(byte)reason.charAt(j);
+            bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
+            bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
+
+            __status[i] = new Status();
+            __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE);
+            __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE);
+            __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE);
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public static Buffer getReasonBuffer(int code)
+    {
+        Status status = code<__status.length?__status[code]:null;
+        if (status!=null)
+            return status._reason;
+        return null;
+    }
+
+
+    // common _content
+    private static final byte[] LAST_CHUNK =
+    { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
+    private static final byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
+    private static final byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
+    private static final byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
+    private static final byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
+    private static final byte[] CRLF = StringUtil.getBytes("\015\012");
+    private static final byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
+    private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
+
+    // other statics
+    private static final int CHUNK_SPACE = 12;
+
+    public static void setServerVersion(String version)
+    {
+        SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
+    }
+
+    // data
+    protected boolean _bypass = false; // True if _content buffer can be written directly to endp and bypass the content buffer
+    private boolean _needCRLF = false;
+    private boolean _needEOC = false;
+    private boolean _bufferChunked = false;
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Constructor.
+     *
+     * @param buffers buffer pool
+     * @param io the end point to use
+     */
+    public HttpGenerator(Buffers buffers, EndPoint io)
+    {
+        super(buffers,io);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void reset()
+    {
+        if (_persistent!=null && !_persistent && _endp!=null && !_endp.isOutputShutdown())
+        {
+            try
+            {
+                _endp.shutdownOutput();
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        super.reset();
+        if (_buffer!=null)
+            _buffer.clear();
+        if (_header!=null)
+            _header.clear();
+        if (_content!=null)
+            _content=null;
+        _bypass = false;
+        _needCRLF = false;
+        _needEOC = false;
+        _bufferChunked=false;
+        _method=null;
+        _uri=null;
+        _noContent=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add content.
+     *
+     * @param content
+     * @param last
+     * @throws IllegalArgumentException if <code>content</code> is {@link Buffer#isImmutable immutable}.
+     * @throws IllegalStateException If the request is not expecting any more content,
+     *   or if the buffers are full and cannot be flushed.
+     * @throws IOException if there is a problem flushing the buffers.
+     */
+    public void addContent(Buffer content, boolean last) throws IOException
+    {
+        if (_noContent)
+            throw new IllegalStateException("NO CONTENT");
+
+        if (_last || _state==STATE_END)
+        {
+            LOG.warn("Ignoring extra content {}",content);
+            content.clear();
+            return;
+        }
+        _last = last;
+
+        // Handle any unfinished business?
+        if (_content!=null && _content.length()>0 || _bufferChunked)
+        {
+            if (_endp.isOutputShutdown())
+                throw new EofException();
+            flushBuffer();
+            if (_content != null && _content.length()>0)
+            {
+                if (_bufferChunked)
+                {
+                    Buffer nc=_buffers.getBuffer(_content.length()+CHUNK_SPACE+content.length());
+                    nc.put(_content);
+                    nc.put(HttpTokens.CRLF);
+                    BufferUtil.putHexInt(nc, content.length());
+                    nc.put(HttpTokens.CRLF);
+                    nc.put(content);
+                    content=nc;
+                }
+                else
+                {
+                    Buffer nc=_buffers.getBuffer(_content.length()+content.length());
+                    nc.put(_content);
+                    nc.put(content);
+                    content=nc;
+                }
+            }
+        }
+
+        _content = content;
+        _contentWritten += content.length();
+
+        // Handle the _content
+        if (_head)
+        {
+            content.clear();
+            _content=null;
+        }
+        else if (_endp != null && (_buffer==null || _buffer.length()==0) && _content.length() > 0 && (_last || isCommitted() && _content.length()>1024))
+        {
+            _bypass = true;
+        }
+        else if (!_bufferChunked)
+        {
+            // Yes - so we better check we have a buffer
+            if (_buffer == null)
+                _buffer = _buffers.getBuffer();
+
+            // Copy _content to buffer;
+            int len=_buffer.put(_content);
+            _content.skip(len);
+            if (_content.length() == 0)
+                _content = null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * send complete response.
+     *
+     * @param response
+     */
+    public void sendResponse(Buffer response) throws IOException
+    {
+        if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
+            throw new IllegalStateException();
+
+        _last = true;
+
+        _content = response;
+        _bypass = true;
+        _state = STATE_FLUSHING;
+
+        // TODO this is not exactly right, but should do.
+        _contentLength =_contentWritten = response.length();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Prepare buffer for unchecked writes.
+     * Prepare the generator buffer to receive unchecked writes
+     * @return the available space in the buffer.
+     * @throws IOException
+     */
+    @Override
+    public int prepareUncheckedAddContent() throws IOException
+    {
+        if (_noContent)
+            return -1;
+
+        if (_last || _state==STATE_END)
+            return -1;
+
+        // Handle any unfinished business?
+        Buffer content = _content;
+        if (content != null && content.length()>0 || _bufferChunked)
+        {
+            flushBuffer();
+            if (content != null && content.length()>0 || _bufferChunked)
+                throw new IllegalStateException("FULL");
+        }
+
+        // we better check we have a buffer
+        if (_buffer == null)
+            _buffer = _buffers.getBuffer();
+
+        _contentWritten-=_buffer.length();
+
+        // Handle the _content
+        if (_head)
+            return Integer.MAX_VALUE;
+
+        return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isBufferFull()
+    {
+        // Should we flush the buffers?
+        return super.isBufferFull() || _bufferChunked || _bypass  || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void send1xx(int code) throws IOException
+    {
+        if (_state != STATE_HEADER)
+            return;
+
+        if (code<100||code>199)
+            throw new IllegalArgumentException("!1xx");
+        Status status=__status[code];
+        if (status==null)
+            throw new IllegalArgumentException(code+"?");
+
+        // get a header buffer
+        if (_header == null)
+            _header = _buffers.getHeader();
+
+        _header.put(status._responseLine);
+        _header.put(HttpTokens.CRLF);
+
+        try
+        {
+            // nasty semi busy flush!
+            while(_header.length()>0)
+            {
+                int len = _endp.flush(_header);
+                if (len<0)
+                    throw new EofException();
+                if (len==0)
+                    Thread.sleep(100);
+            }
+        }
+        catch(InterruptedException e)
+        {
+            LOG.debug(e);
+            throw new InterruptedIOException(e.toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isRequest()
+    {
+        return _method!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isResponse()
+    {
+        return _method==null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
+    {
+        if (_state != STATE_HEADER)
+            return;
+
+        // handle a reset
+        if (isResponse() && _status==0)
+            throw new EofException();
+
+        if (_last && !allContentAdded)
+            throw new IllegalStateException("last?");
+        _last = _last | allContentAdded;
+
+        // get a header buffer
+        if (_header == null)
+            _header = _buffers.getHeader();
+
+        boolean has_server = false;
+
+        try
+        {
+            if (isRequest())
+            {
+                _persistent=true;
+
+                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
+                {
+                    _contentLength = HttpTokens.NO_CONTENT;
+                    _header.put(_method);
+                    _header.put((byte)' ');
+                    _header.put(_uri.getBytes("UTF-8")); // TODO check
+                    _header.put(HttpTokens.CRLF);
+                    _state = STATE_FLUSHING;
+                    _noContent=true;
+                    return;
+                }
+                else
+                {
+                    _header.put(_method);
+                    _header.put((byte)' ');
+                    _header.put(_uri.getBytes("UTF-8")); // TODO check
+                    _header.put((byte)' ');
+                    _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
+                    _header.put(HttpTokens.CRLF);
+                }
+            }
+            else
+            {
+                // Responses
+                if (_version == HttpVersions.HTTP_0_9_ORDINAL)
+                {
+                    _persistent = false;
+                    _contentLength = HttpTokens.EOF_CONTENT;
+                    _state = STATE_CONTENT;
+                    return;
+                }
+                else
+                {
+                    if (_persistent==null)
+                        _persistent= (_version > HttpVersions.HTTP_1_0_ORDINAL);
+
+                    // add response line
+                    Status status = _status<__status.length?__status[_status]:null;
+
+                    if (status==null)
+                    {
+                        _header.put(HttpVersions.HTTP_1_1_BUFFER);
+                        _header.put((byte) ' ');
+                        _header.put((byte) ('0' + _status / 100));
+                        _header.put((byte) ('0' + (_status % 100) / 10));
+                        _header.put((byte) ('0' + (_status % 10)));
+                        _header.put((byte) ' ');
+                        if (_reason==null)
+                        {
+                            _header.put((byte) ('0' + _status / 100));
+                            _header.put((byte) ('0' + (_status % 100) / 10));
+                            _header.put((byte) ('0' + (_status % 10)));
+                        }
+                        else
+                            _header.put(_reason);
+                        _header.put(HttpTokens.CRLF);
+                    }
+                    else
+                    {
+                        if (_reason==null)
+                            _header.put(status._responseLine);
+                        else
+                        {
+                            _header.put(status._schemeCode);
+                            _header.put(_reason);
+                            _header.put(HttpTokens.CRLF);
+                        }
+                    }
+
+                    if (_status<200 && _status>=100 )
+                    {
+                        _noContent=true;
+                        _content=null;
+                        if (_buffer!=null)
+                            _buffer.clear();
+                        // end the header.
+
+                        if (_status!=101 )
+                        {
+                            _header.put(HttpTokens.CRLF);
+                            _state = STATE_CONTENT;
+                            return;
+                        }
+                    }
+                    else if (_status==204 || _status==304)
+                    {
+                        _noContent=true;
+                        _content=null;
+                        if (_buffer!=null)
+                            _buffer.clear();
+                    }
+                }
+            }
+
+            // Add headers
+            if (_status>=200 && _date!=null)
+            {
+                _header.put(HttpHeaders.DATE_BUFFER);
+                _header.put((byte)':');
+                _header.put((byte)' ');
+                _header.put(_date);
+                _header.put(CRLF);
+            }
+
+            // key field values
+            HttpFields.Field content_length = null;
+            HttpFields.Field transfer_encoding = null;
+            boolean keep_alive = false;
+            boolean close=false;
+            boolean content_type=false;
+            StringBuilder connection = null;
+
+            if (fields != null)
+            {
+                int s=fields.size();
+                for (int f=0;f<s;f++)
+                {
+                    HttpFields.Field field = fields.getField(f);
+                    if (field==null)
+                        continue;
+
+                    switch (field.getNameOrdinal())
+                    {
+                        case HttpHeaders.CONTENT_LENGTH_ORDINAL:
+                            content_length = field;
+                            _contentLength = field.getLongValue();
+
+                            if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
+                                content_length = null;
+
+                            // write the field to the header buffer
+                            field.putTo(_header);
+                            break;
+
+                        case HttpHeaders.CONTENT_TYPE_ORDINAL:
+                            if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
+
+                            // write the field to the header buffer
+                            content_type=true;
+                            field.putTo(_header);
+                            break;
+
+                        case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
+                            if (_version == HttpVersions.HTTP_1_1_ORDINAL)
+                                transfer_encoding = field;
+                            // Do NOT add yet!
+                            break;
+
+                        case HttpHeaders.CONNECTION_ORDINAL:
+                            if (isRequest())
+                                field.putTo(_header);
+
+                            int connection_value = field.getValueOrdinal();
+                            switch (connection_value)
+                            {
+                                case -1:
+                                {
+                                    String[] values = field.getValue().split(",");
+                                    for  (int i=0;values!=null && i<values.length;i++)
+                                    {
+                                        CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
+
+                                        if (cb!=null)
+                                        {
+                                            switch(cb.getOrdinal())
+                                            {
+                                                case HttpHeaderValues.CLOSE_ORDINAL:
+                                                    close=true;
+                                                    if (isResponse())
+                                                        _persistent=false;
+                                                    keep_alive=false;
+                                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
+                                                        _contentLength = HttpTokens.EOF_CONTENT;
+                                                    break;
+
+                                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+                                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
+                                                    {
+                                                        keep_alive = true;
+                                                        if (isResponse())
+                                                            _persistent = true;
+                                                    }
+                                                    break;
+
+                                                default:
+                                                    if (connection==null)
+                                                        connection=new StringBuilder();
+                                                    else
+                                                        connection.append(',');
+                                                    connection.append(values[i]);
+                                            }
+                                        }
+                                        else
+                                        {
+                                            if (connection==null)
+                                                connection=new StringBuilder();
+                                            else
+                                                connection.append(',');
+                                            connection.append(values[i]);
+                                        }
+                                    }
+
+                                    break;
+                                }
+                                case HttpHeaderValues.UPGRADE_ORDINAL:
+                                {
+                                    // special case for websocket connection ordering
+                                    if (isResponse())
+                                    {
+                                        field.putTo(_header);
+                                        continue;
+                                    }
+                                }
+                                case HttpHeaderValues.CLOSE_ORDINAL:
+                                {
+                                    close=true;
+                                    if (isResponse())
+                                        _persistent=false;
+                                    if (!_persistent && isResponse() && _contentLength == HttpTokens.UNKNOWN_CONTENT)
+                                        _contentLength = HttpTokens.EOF_CONTENT;
+                                    break;
+                                }
+                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+                                {
+                                    if (_version == HttpVersions.HTTP_1_0_ORDINAL)
+                                    {
+                                        keep_alive = true;
+                                        if (isResponse())
+                                            _persistent=true;
+                                    }
+                                    break;
+                                }
+                                default:
+                                {
+                                    if (connection==null)
+                                        connection=new StringBuilder();
+                                    else
+                                        connection.append(',');
+                                    connection.append(field.getValue());
+                                }
+                            }
+
+                            // Do NOT add yet!
+                            break;
+
+                        case HttpHeaders.SERVER_ORDINAL:
+                            if (getSendServerVersion())
+                            {
+                                has_server=true;
+                                field.putTo(_header);
+                            }
+                            break;
+
+                        default:
+                            // write the field to the header buffer
+                            field.putTo(_header);
+                    }
+                }
+            }
+
+            // Calculate how to end _content and connection, _content length and transfer encoding
+            // settings.
+            // From RFC 2616 4.4:
+            // 1. No body for 1xx, 204, 304 & HEAD response
+            // 2. Force _content-length?
+            // 3. If Transfer-Encoding!=identity && HTTP/1.1 && !HttpConnection==close then chunk
+            // 4. Content-Length
+            // 5. multipart/byteranges
+            // 6. close
+            switch ((int) _contentLength)
+            {
+                case HttpTokens.UNKNOWN_CONTENT:
+                    // It may be that we have no _content, or perhaps _content just has not been
+                    // written yet?
+
+                    // Response known not to have a body
+                    if (_contentWritten == 0 && isResponse() && (_status < 200 || _status == 204 || _status == 304))
+                        _contentLength = HttpTokens.NO_CONTENT;
+                    else if (_last)
+                    {
+                        // we have seen all the _content there is
+                        _contentLength = _contentWritten;
+                        if (content_length == null && (isResponse() || _contentLength>0 || content_type ) && !_noContent)
+                        {
+                            // known length but not actually set.
+                            _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
+                            _header.put(HttpTokens.COLON);
+                            _header.put((byte) ' ');
+                            BufferUtil.putDecLong(_header, _contentLength);
+                            _header.put(HttpTokens.CRLF);
+                        }
+                    }
+                    else
+                    {
+                        // No idea, so we must assume that a body is coming
+                        _contentLength = (!_persistent || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
+                        if (isRequest() && _contentLength==HttpTokens.EOF_CONTENT)
+                        {
+                            _contentLength=HttpTokens.NO_CONTENT;
+                            _noContent=true;
+                        }
+                    }
+                    break;
+
+                case HttpTokens.NO_CONTENT:
+                    if (content_length == null && isResponse() && _status >= 200 && _status != 204 && _status != 304)
+                        _header.put(CONTENT_LENGTH_0);
+                    break;
+
+                case HttpTokens.EOF_CONTENT:
+                    _persistent = isRequest();
+                    break;
+
+                case HttpTokens.CHUNKED_CONTENT:
+                    break;
+
+                default:
+                    // TODO - maybe allow forced chunking by setting te ???
+                    break;
+            }
+
+            // Add transfer_encoding if needed
+            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+            {
+                // try to use user supplied encoding as it may have other values.
+                if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
+                {
+                    String c = transfer_encoding.getValue();
+                    if (c.endsWith(HttpHeaderValues.CHUNKED))
+                        transfer_encoding.putTo(_header);
+                    else
+                        throw new IllegalArgumentException("BAD TE");
+                }
+                else
+                    _header.put(TRANSFER_ENCODING_CHUNKED);
+            }
+
+            // Handle connection if need be
+            if (_contentLength==HttpTokens.EOF_CONTENT)
+            {
+                keep_alive=false;
+                _persistent=false;
+            }
+
+            if (isResponse())
+            {
+                if (!_persistent && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
+                {
+                    _header.put(CONNECTION_CLOSE);
+                    if (connection!=null)
+                    {
+                        _header.setPutIndex(_header.putIndex()-2);
+                        _header.put((byte)',');
+                        _header.put(connection.toString().getBytes());
+                        _header.put(CRLF);
+                    }
+                }
+                else if (keep_alive)
+                {
+                    _header.put(CONNECTION_KEEP_ALIVE);
+                    if (connection!=null)
+                    {
+                        _header.setPutIndex(_header.putIndex()-2);
+                        _header.put((byte)',');
+                        _header.put(connection.toString().getBytes());
+                        _header.put(CRLF);
+                    }
+                }
+                else if (connection!=null)
+                {
+                    _header.put(CONNECTION_);
+                    _header.put(connection.toString().getBytes());
+                    _header.put(CRLF);
+                }
+            }
+
+            if (!has_server && _status>199 && getSendServerVersion())
+                _header.put(SERVER);
+
+            // end the header.
+            _header.put(HttpTokens.CRLF);
+            _state = STATE_CONTENT;
+
+        }
+        catch(ArrayIndexOutOfBoundsException e)
+        {
+            throw new RuntimeException("Header>"+_header.capacity(),e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Complete the message.
+     *
+     * @throws IOException
+     */
+    @Override
+    public void complete() throws IOException
+    {
+        if (_state == STATE_END)
+            return;
+
+        super.complete();
+
+        if (_state < STATE_FLUSHING)
+        {
+            _state = STATE_FLUSHING;
+            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+                _needEOC = true;
+        }
+
+        flushBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int flushBuffer() throws IOException
+    {
+        try
+        {
+
+            if (_state == STATE_HEADER)
+                throw new IllegalStateException("State==HEADER");
+
+            prepareBuffers();
+
+            if (_endp == null)
+            {
+                if (_needCRLF && _buffer!=null)
+                    _buffer.put(HttpTokens.CRLF);
+                if (_needEOC && _buffer!=null && !_head)
+                    _buffer.put(LAST_CHUNK);
+                _needCRLF=false;
+                _needEOC=false;
+                return 0;
+            }
+
+            int total= 0;
+
+            int len = -1;
+            int to_flush = flushMask();
+            int last_flush;
+
+            do
+            {
+                last_flush=to_flush;
+                switch (to_flush)
+                {
+                    case 7:
+                        throw new IllegalStateException(); // should never happen!
+                    case 6:
+                        len = _endp.flush(_header, _buffer, null);
+                        break;
+                    case 5:
+                        len = _endp.flush(_header, _content, null);
+                        break;
+                    case 4:
+                        len = _endp.flush(_header);
+                        break;
+                    case 3:
+                        len = _endp.flush(_buffer, _content, null);
+                        break;
+                    case 2:
+                        len = _endp.flush(_buffer);
+                        break;
+                    case 1:
+                        len = _endp.flush(_content);
+                        break;
+                    case 0:
+                    {
+                        len=0;
+                        // Nothing more we can write now.
+                        if (_header != null)
+                            _header.clear();
+
+                        _bypass = false;
+                        _bufferChunked = false;
+
+                        if (_buffer != null)
+                        {
+                            _buffer.clear();
+                            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+                            {
+                                // reserve some space for the chunk header
+                                _buffer.setPutIndex(CHUNK_SPACE);
+                                _buffer.setGetIndex(CHUNK_SPACE);
+
+                                // Special case handling for small left over buffer from
+                                // an addContent that caused a buffer flush.
+                                if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
+                                {
+                                    _buffer.put(_content);
+                                    _content.clear();
+                                    _content=null;
+                                }
+                            }
+                        }
+
+                        // Are we completely finished for now?
+                        if (!_needCRLF && !_needEOC && (_content==null || _content.length()==0))
+                        {
+                            if (_state == STATE_FLUSHING)
+                                _state = STATE_END;
+
+                            if (_state==STATE_END && _persistent != null && !_persistent && _status!=100 && _method==null)
+                                _endp.shutdownOutput();
+                        }
+                        else
+                            // Try to prepare more to write.
+                            prepareBuffers();
+                    }
+
+                }
+
+                if (len > 0)
+                    total+=len;
+
+                to_flush = flushMask();
+            }
+            // loop while progress is being made (OR we have prepared some buffers that might make progress)
+            while (len>0 || (to_flush!=0 && last_flush==0));
+
+            return total;
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+            throw (e instanceof EofException) ? e:new EofException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private int flushMask()
+    {
+        return  ((_header != null && _header.length() > 0)?4:0)
+        | ((_buffer != null && _buffer.length() > 0)?2:0)
+        | ((_bypass && _content != null && _content.length() > 0)?1:0);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void prepareBuffers()
+    {
+        // if we are not flushing an existing chunk
+        if (!_bufferChunked)
+        {
+            // Refill buffer if possible
+            if (!_bypass && _content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
+            {
+                int len = _buffer.put(_content);
+                _content.skip(len);
+                if (_content.length() == 0)
+                    _content = null;
+            }
+
+            // Chunk buffer if need be
+            if (_contentLength == HttpTokens.CHUNKED_CONTENT)
+            {
+                if (_bypass && (_buffer==null||_buffer.length()==0) && _content!=null)
+                {
+                    // this is a bypass write
+                    int size = _content.length();
+                    _bufferChunked = true;
+
+                    if (_header == null)
+                        _header = _buffers.getHeader();
+
+                    // if we need CRLF add this to header
+                    if (_needCRLF)
+                    {
+                        if (_header.length() > 0) throw new IllegalStateException("EOC");
+                        _header.put(HttpTokens.CRLF);
+                        _needCRLF = false;
+                    }
+                    // Add the chunk size to the header
+                    BufferUtil.putHexInt(_header, size);
+                    _header.put(HttpTokens.CRLF);
+
+                    // Need a CRLF after the content
+                    _needCRLF=true;
+                }
+                else if (_buffer!=null)
+                {
+                    int size = _buffer.length();
+                    if (size > 0)
+                    {
+                        // Prepare a chunk!
+                        _bufferChunked = true;
+
+                        // Did we leave space at the start of the buffer.
+                        //noinspection ConstantConditions
+                        if (_buffer.getIndex() == CHUNK_SPACE)
+                        {
+                            // Oh yes, goodie! let's use it then!
+                            _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
+                            _buffer.setGetIndex(_buffer.getIndex() - 2);
+                            BufferUtil.prependHexInt(_buffer, size);
+
+                            if (_needCRLF)
+                            {
+                                _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
+                                _buffer.setGetIndex(_buffer.getIndex() - 2);
+                                _needCRLF = false;
+                            }
+                        }
+                        else
+                        {
+                            // No space so lets use a header buffer.
+                            if (_header == null)
+                                _header = _buffers.getHeader();
+
+                            if (_needCRLF)
+                            {
+                                if (_header.length() > 0) throw new IllegalStateException("EOC");
+                                _header.put(HttpTokens.CRLF);
+                                _needCRLF = false;
+                            }
+                            BufferUtil.putHexInt(_header, size);
+                            _header.put(HttpTokens.CRLF);
+                        }
+
+                        // Add end chunk trailer.
+                        if (_buffer.space() >= 2)
+                            _buffer.put(HttpTokens.CRLF);
+                        else
+                            _needCRLF = true;
+                    }
+                }
+
+                // If we need EOC and everything written
+                if (_needEOC && (_content == null || _content.length() == 0))
+                {
+                    if (_header == null && _buffer == null)
+                        _header = _buffers.getHeader();
+
+                    if (_needCRLF)
+                    {
+                        if (_buffer == null && _header != null && _header.space() >= HttpTokens.CRLF.length)
+                        {
+                            _header.put(HttpTokens.CRLF);
+                            _needCRLF = false;
+                        }
+                        else if (_buffer!=null && _buffer.space() >= HttpTokens.CRLF.length)
+                        {
+                            _buffer.put(HttpTokens.CRLF);
+                            _needCRLF = false;
+                        }
+                    }
+
+                    if (!_needCRLF && _needEOC)
+                    {
+                        if (_buffer == null && _header != null && _header.space() >= LAST_CHUNK.length)
+                        {
+                            if (!_head)
+                            {
+                                _header.put(LAST_CHUNK);
+                                _bufferChunked=true;
+                            }
+                            _needEOC = false;
+                        }
+                        else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
+                        {
+                            if (!_head)
+                            {
+                                _buffer.put(LAST_CHUNK);
+                                _bufferChunked=true;
+                            }
+                            _needEOC = false;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (_content != null && _content.length() == 0)
+            _content = null;
+
+    }
+
+    public int getBytesBuffered()
+    {
+        return(_header==null?0:_header.length())+
+        (_buffer==null?0:_buffer.length())+
+        (_content==null?0:_content.length());
+    }
+
+    public boolean isEmpty()
+    {
+        return (_header==null||_header.length()==0) &&
+        (_buffer==null||_buffer.length()==0) &&
+        (_content==null||_content.length()==0);
+    }
+
+    @Override
+    public String toString()
+    {
+        Buffer header=_header;
+        Buffer buffer=_buffer;
+        Buffer content=_content;
+        return String.format("%s{s=%d,h=%d,b=%d,c=%d}",
+                getClass().getSimpleName(),
+                _state,
+                header == null ? -1 : header.length(),
+                buffer == null ? -1 : buffer.length(),
+                content == null ? -1 : content.length());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpHeaderValues.java b/src/java/org/eclipse/jetty/http/HttpHeaderValues.java
new file mode 100644
index 0000000..995b744
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpHeaderValues.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/**
+ * Cached HTTP Header values.
+ * This class caches the conversion of common HTTP Header values to and from {@link ByteArrayBuffer} instances.
+ * The resource "/org/eclipse/jetty/useragents" is checked for a list of common user agents, so that repeated
+ * creation of strings for these agents can be avoided.
+ * 
+ * 
+ */
+public class HttpHeaderValues extends BufferCache
+{
+    public final static String
+        CLOSE="close",
+        CHUNKED="chunked",
+        GZIP="gzip",
+        IDENTITY="identity",
+        KEEP_ALIVE="keep-alive",
+        CONTINUE="100-continue",
+        PROCESSING="102-processing",
+        TE="TE",
+        BYTES="bytes",
+        NO_CACHE="no-cache",
+        UPGRADE="Upgrade";
+
+    public final static int
+        CLOSE_ORDINAL=1,
+        CHUNKED_ORDINAL=2,
+        GZIP_ORDINAL=3,
+        IDENTITY_ORDINAL=4,
+        KEEP_ALIVE_ORDINAL=5,
+        CONTINUE_ORDINAL=6,
+        PROCESSING_ORDINAL=7,
+        TE_ORDINAL=8,
+        BYTES_ORDINAL=9,
+        NO_CACHE_ORDINAL=10,
+        UPGRADE_ORDINAL=11;
+    
+    public final static HttpHeaderValues CACHE= new HttpHeaderValues();
+
+    public final static Buffer 
+        CLOSE_BUFFER=CACHE.add(CLOSE,CLOSE_ORDINAL),
+        CHUNKED_BUFFER=CACHE.add(CHUNKED,CHUNKED_ORDINAL),
+        GZIP_BUFFER=CACHE.add(GZIP,GZIP_ORDINAL),
+        IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
+        KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
+        CONTINUE_BUFFER=CACHE.add(CONTINUE, CONTINUE_ORDINAL),
+        PROCESSING_BUFFER=CACHE.add(PROCESSING, PROCESSING_ORDINAL),
+        TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
+        BYTES_BUFFER=CACHE.add(BYTES,BYTES_ORDINAL),
+        NO_CACHE_BUFFER=CACHE.add(NO_CACHE,NO_CACHE_ORDINAL),
+        UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL);
+        
+
+    public static boolean hasKnownValues(int httpHeaderOrdinal)
+    {
+        switch(httpHeaderOrdinal)
+        {
+            case HttpHeaders.CONNECTION_ORDINAL:
+            case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
+            case HttpHeaders.CONTENT_ENCODING_ORDINAL:
+                return true;
+        }
+        return false;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpHeaders.java b/src/java/org/eclipse/jetty/http/HttpHeaders.java
new file mode 100644
index 0000000..719e91b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpHeaders.java
@@ -0,0 +1,241 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ */
+public class HttpHeaders extends BufferCache
+{
+    /* ------------------------------------------------------------ */
+    /** General Fields.
+     */
+    public final static String 
+        CONNECTION= "Connection",
+        CACHE_CONTROL= "Cache-Control",
+        DATE= "Date",
+        PRAGMA= "Pragma",
+        PROXY_CONNECTION = "Proxy-Connection",
+        TRAILER= "Trailer",
+        TRANSFER_ENCODING= "Transfer-Encoding",
+        UPGRADE= "Upgrade",
+        VIA= "Via",
+        WARNING= "Warning",
+        NEGOTIATE= "Negotiate";
+
+    /* ------------------------------------------------------------ */
+    /** Entity Fields.
+     */
+    public final static String ALLOW= "Allow",
+        CONTENT_ENCODING= "Content-Encoding",
+        CONTENT_LANGUAGE= "Content-Language",
+        CONTENT_LENGTH= "Content-Length",
+        CONTENT_LOCATION= "Content-Location",
+        CONTENT_MD5= "Content-MD5",
+        CONTENT_RANGE= "Content-Range",
+        CONTENT_TYPE= "Content-Type",
+        EXPIRES= "Expires",
+        LAST_MODIFIED= "Last-Modified";
+
+    /* ------------------------------------------------------------ */
+    /** Request Fields.
+     */
+    public final static String ACCEPT= "Accept",
+        ACCEPT_CHARSET= "Accept-Charset",
+        ACCEPT_ENCODING= "Accept-Encoding",
+        ACCEPT_LANGUAGE= "Accept-Language",
+        AUTHORIZATION= "Authorization",
+        EXPECT= "Expect",
+        FORWARDED= "Forwarded",
+        FROM= "From",
+        HOST= "Host",
+        IF_MATCH= "If-Match",
+        IF_MODIFIED_SINCE= "If-Modified-Since",
+        IF_NONE_MATCH= "If-None-Match",
+        IF_RANGE= "If-Range",
+        IF_UNMODIFIED_SINCE= "If-Unmodified-Since",
+        KEEP_ALIVE= "Keep-Alive",
+        MAX_FORWARDS= "Max-Forwards",
+        PROXY_AUTHORIZATION= "Proxy-Authorization",
+        RANGE= "Range",
+        REQUEST_RANGE= "Request-Range",
+        REFERER= "Referer",
+        TE= "TE",
+        USER_AGENT= "User-Agent",
+        X_FORWARDED_FOR= "X-Forwarded-For",
+        X_FORWARDED_PROTO= "X-Forwarded-Proto",
+        X_FORWARDED_SERVER= "X-Forwarded-Server",
+        X_FORWARDED_HOST= "X-Forwarded-Host";
+
+    /* ------------------------------------------------------------ */
+    /** Response Fields.
+     */
+    public final static String ACCEPT_RANGES= "Accept-Ranges",
+        AGE= "Age",
+        ETAG= "ETag",
+        LOCATION= "Location",
+        PROXY_AUTHENTICATE= "Proxy-Authenticate",
+        RETRY_AFTER= "Retry-After",
+        SERVER= "Server",
+        SERVLET_ENGINE= "Servlet-Engine",
+        VARY= "Vary",
+        WWW_AUTHENTICATE= "WWW-Authenticate";
+
+    /* ------------------------------------------------------------ */
+    /** Other Fields.
+     */
+    public final static String COOKIE= "Cookie",
+        SET_COOKIE= "Set-Cookie",
+        SET_COOKIE2= "Set-Cookie2",
+        MIME_VERSION= "MIME-Version",
+        IDENTITY= "identity";
+
+    public final static int CONNECTION_ORDINAL= 1,
+        DATE_ORDINAL= 2,
+        PRAGMA_ORDINAL= 3,
+        TRAILER_ORDINAL= 4,
+        TRANSFER_ENCODING_ORDINAL= 5,
+        UPGRADE_ORDINAL= 6,
+        VIA_ORDINAL= 7,
+        WARNING_ORDINAL= 8,
+        ALLOW_ORDINAL= 9,
+        CONTENT_ENCODING_ORDINAL= 10,
+        CONTENT_LANGUAGE_ORDINAL= 11,
+        CONTENT_LENGTH_ORDINAL= 12,
+        CONTENT_LOCATION_ORDINAL= 13,
+        CONTENT_MD5_ORDINAL= 14,
+        CONTENT_RANGE_ORDINAL= 15,
+        CONTENT_TYPE_ORDINAL= 16,
+        EXPIRES_ORDINAL= 17,
+        LAST_MODIFIED_ORDINAL= 18,
+        ACCEPT_ORDINAL= 19,
+        ACCEPT_CHARSET_ORDINAL= 20,
+        ACCEPT_ENCODING_ORDINAL= 21,
+        ACCEPT_LANGUAGE_ORDINAL= 22,
+        AUTHORIZATION_ORDINAL= 23,
+        EXPECT_ORDINAL= 24,
+        FORWARDED_ORDINAL= 25,
+        FROM_ORDINAL= 26,
+        HOST_ORDINAL= 27,
+        IF_MATCH_ORDINAL= 28,
+        IF_MODIFIED_SINCE_ORDINAL= 29,
+        IF_NONE_MATCH_ORDINAL= 30,
+        IF_RANGE_ORDINAL= 31,
+        IF_UNMODIFIED_SINCE_ORDINAL= 32,
+        KEEP_ALIVE_ORDINAL= 33,
+        MAX_FORWARDS_ORDINAL= 34,
+        PROXY_AUTHORIZATION_ORDINAL= 35,
+        RANGE_ORDINAL= 36,
+        REQUEST_RANGE_ORDINAL= 37,
+        REFERER_ORDINAL= 38,
+        TE_ORDINAL= 39,
+        USER_AGENT_ORDINAL= 40,
+        X_FORWARDED_FOR_ORDINAL= 41,
+        ACCEPT_RANGES_ORDINAL= 42,
+        AGE_ORDINAL= 43,
+        ETAG_ORDINAL= 44,
+        LOCATION_ORDINAL= 45,
+        PROXY_AUTHENTICATE_ORDINAL= 46,
+        RETRY_AFTER_ORDINAL= 47,
+        SERVER_ORDINAL= 48,
+        SERVLET_ENGINE_ORDINAL= 49,
+        VARY_ORDINAL= 50,
+        WWW_AUTHENTICATE_ORDINAL= 51,
+        COOKIE_ORDINAL= 52,
+        SET_COOKIE_ORDINAL= 53,
+        SET_COOKIE2_ORDINAL= 54,
+        MIME_VERSION_ORDINAL= 55,
+        IDENTITY_ORDINAL= 56,
+        CACHE_CONTROL_ORDINAL=57,
+        PROXY_CONNECTION_ORDINAL=58,
+        X_FORWARDED_PROTO_ORDINAL=59,
+        X_FORWARDED_SERVER_ORDINAL=60,
+        X_FORWARDED_HOST_ORDINAL=61;
+
+    public final static HttpHeaders CACHE= new HttpHeaders();
+    
+    public final static Buffer
+        HOST_BUFFER=CACHE.add(HOST,HOST_ORDINAL),
+        ACCEPT_BUFFER=CACHE.add(ACCEPT,ACCEPT_ORDINAL),
+        ACCEPT_CHARSET_BUFFER=CACHE.add(ACCEPT_CHARSET,ACCEPT_CHARSET_ORDINAL),
+        ACCEPT_ENCODING_BUFFER=CACHE.add(ACCEPT_ENCODING,ACCEPT_ENCODING_ORDINAL),
+        ACCEPT_LANGUAGE_BUFFER=CACHE.add(ACCEPT_LANGUAGE,ACCEPT_LANGUAGE_ORDINAL),
+        
+        CONTENT_LENGTH_BUFFER=CACHE.add(CONTENT_LENGTH,CONTENT_LENGTH_ORDINAL),
+        CONNECTION_BUFFER=CACHE.add(CONNECTION,CONNECTION_ORDINAL),
+        CACHE_CONTROL_BUFFER=CACHE.add(CACHE_CONTROL,CACHE_CONTROL_ORDINAL),
+        DATE_BUFFER=CACHE.add(DATE,DATE_ORDINAL),
+        PRAGMA_BUFFER=CACHE.add(PRAGMA,PRAGMA_ORDINAL),
+        TRAILER_BUFFER=CACHE.add(TRAILER,TRAILER_ORDINAL),
+        TRANSFER_ENCODING_BUFFER=CACHE.add(TRANSFER_ENCODING,TRANSFER_ENCODING_ORDINAL),
+        UPGRADE_BUFFER=CACHE.add(UPGRADE,UPGRADE_ORDINAL),
+        VIA_BUFFER=CACHE.add(VIA,VIA_ORDINAL),
+        WARNING_BUFFER=CACHE.add(WARNING,WARNING_ORDINAL),
+        ALLOW_BUFFER=CACHE.add(ALLOW,ALLOW_ORDINAL),
+        CONTENT_ENCODING_BUFFER=CACHE.add(CONTENT_ENCODING,CONTENT_ENCODING_ORDINAL),
+        CONTENT_LANGUAGE_BUFFER=CACHE.add(CONTENT_LANGUAGE,CONTENT_LANGUAGE_ORDINAL),
+        CONTENT_LOCATION_BUFFER=CACHE.add(CONTENT_LOCATION,CONTENT_LOCATION_ORDINAL),
+        CONTENT_MD5_BUFFER=CACHE.add(CONTENT_MD5,CONTENT_MD5_ORDINAL),
+        CONTENT_RANGE_BUFFER=CACHE.add(CONTENT_RANGE,CONTENT_RANGE_ORDINAL),
+        CONTENT_TYPE_BUFFER=CACHE.add(CONTENT_TYPE,CONTENT_TYPE_ORDINAL),
+        EXPIRES_BUFFER=CACHE.add(EXPIRES,EXPIRES_ORDINAL),
+        LAST_MODIFIED_BUFFER=CACHE.add(LAST_MODIFIED,LAST_MODIFIED_ORDINAL),
+        AUTHORIZATION_BUFFER=CACHE.add(AUTHORIZATION,AUTHORIZATION_ORDINAL),
+        EXPECT_BUFFER=CACHE.add(EXPECT,EXPECT_ORDINAL),
+        FORWARDED_BUFFER=CACHE.add(FORWARDED,FORWARDED_ORDINAL),
+        FROM_BUFFER=CACHE.add(FROM,FROM_ORDINAL),
+        IF_MATCH_BUFFER=CACHE.add(IF_MATCH,IF_MATCH_ORDINAL),
+        IF_MODIFIED_SINCE_BUFFER=CACHE.add(IF_MODIFIED_SINCE,IF_MODIFIED_SINCE_ORDINAL),
+        IF_NONE_MATCH_BUFFER=CACHE.add(IF_NONE_MATCH,IF_NONE_MATCH_ORDINAL),
+        IF_RANGE_BUFFER=CACHE.add(IF_RANGE,IF_RANGE_ORDINAL),
+        IF_UNMODIFIED_SINCE_BUFFER=CACHE.add(IF_UNMODIFIED_SINCE,IF_UNMODIFIED_SINCE_ORDINAL),
+        KEEP_ALIVE_BUFFER=CACHE.add(KEEP_ALIVE,KEEP_ALIVE_ORDINAL),
+        MAX_FORWARDS_BUFFER=CACHE.add(MAX_FORWARDS,MAX_FORWARDS_ORDINAL),
+        PROXY_AUTHORIZATION_BUFFER=CACHE.add(PROXY_AUTHORIZATION,PROXY_AUTHORIZATION_ORDINAL),
+        RANGE_BUFFER=CACHE.add(RANGE,RANGE_ORDINAL),
+        REQUEST_RANGE_BUFFER=CACHE.add(REQUEST_RANGE,REQUEST_RANGE_ORDINAL),
+        REFERER_BUFFER=CACHE.add(REFERER,REFERER_ORDINAL),
+        TE_BUFFER=CACHE.add(TE,TE_ORDINAL),
+        USER_AGENT_BUFFER=CACHE.add(USER_AGENT,USER_AGENT_ORDINAL),
+        X_FORWARDED_FOR_BUFFER=CACHE.add(X_FORWARDED_FOR,X_FORWARDED_FOR_ORDINAL),
+        X_FORWARDED_PROTO_BUFFER=CACHE.add(X_FORWARDED_PROTO,X_FORWARDED_PROTO_ORDINAL),
+        X_FORWARDED_SERVER_BUFFER=CACHE.add(X_FORWARDED_SERVER,X_FORWARDED_SERVER_ORDINAL),
+        X_FORWARDED_HOST_BUFFER=CACHE.add(X_FORWARDED_HOST,X_FORWARDED_HOST_ORDINAL),
+        ACCEPT_RANGES_BUFFER=CACHE.add(ACCEPT_RANGES,ACCEPT_RANGES_ORDINAL),
+        AGE_BUFFER=CACHE.add(AGE,AGE_ORDINAL),
+        ETAG_BUFFER=CACHE.add(ETAG,ETAG_ORDINAL),
+        LOCATION_BUFFER=CACHE.add(LOCATION,LOCATION_ORDINAL),
+        PROXY_AUTHENTICATE_BUFFER=CACHE.add(PROXY_AUTHENTICATE,PROXY_AUTHENTICATE_ORDINAL),
+        RETRY_AFTER_BUFFER=CACHE.add(RETRY_AFTER,RETRY_AFTER_ORDINAL),
+        SERVER_BUFFER=CACHE.add(SERVER,SERVER_ORDINAL),
+        SERVLET_ENGINE_BUFFER=CACHE.add(SERVLET_ENGINE,SERVLET_ENGINE_ORDINAL),
+        VARY_BUFFER=CACHE.add(VARY,VARY_ORDINAL),
+        WWW_AUTHENTICATE_BUFFER=CACHE.add(WWW_AUTHENTICATE,WWW_AUTHENTICATE_ORDINAL),
+        COOKIE_BUFFER=CACHE.add(COOKIE,COOKIE_ORDINAL),
+        SET_COOKIE_BUFFER=CACHE.add(SET_COOKIE,SET_COOKIE_ORDINAL),
+        SET_COOKIE2_BUFFER=CACHE.add(SET_COOKIE2,SET_COOKIE2_ORDINAL),
+        MIME_VERSION_BUFFER=CACHE.add(MIME_VERSION,MIME_VERSION_ORDINAL),
+        IDENTITY_BUFFER=CACHE.add(IDENTITY,IDENTITY_ORDINAL),
+        PROXY_CONNECTION_BUFFER=CACHE.add(PROXY_CONNECTION,PROXY_CONNECTION_ORDINAL);
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpMethods.java b/src/java/org/eclipse/jetty/http/HttpMethods.java
new file mode 100644
index 0000000..2042bf1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpMethods.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * 
+ * 
+ */
+public class HttpMethods
+{
+    public final static String GET= "GET",
+        POST= "POST",
+        HEAD= "HEAD",
+        PUT= "PUT",
+        OPTIONS= "OPTIONS",
+        DELETE= "DELETE",
+        TRACE= "TRACE",
+        CONNECT= "CONNECT",
+        MOVE= "MOVE";
+
+    public final static int GET_ORDINAL= 1,
+        POST_ORDINAL= 2,
+        HEAD_ORDINAL= 3,
+        PUT_ORDINAL= 4,
+        OPTIONS_ORDINAL= 5,
+        DELETE_ORDINAL= 6,
+        TRACE_ORDINAL= 7,
+        CONNECT_ORDINAL= 8,
+        MOVE_ORDINAL= 9;
+
+    public final static BufferCache CACHE= new BufferCache();
+
+    public final static Buffer 
+        GET_BUFFER= CACHE.add(GET, GET_ORDINAL),
+        POST_BUFFER= CACHE.add(POST, POST_ORDINAL),
+        HEAD_BUFFER= CACHE.add(HEAD, HEAD_ORDINAL),
+        PUT_BUFFER= CACHE.add(PUT, PUT_ORDINAL),
+        OPTIONS_BUFFER= CACHE.add(OPTIONS, OPTIONS_ORDINAL),
+        DELETE_BUFFER= CACHE.add(DELETE, DELETE_ORDINAL),
+        TRACE_BUFFER= CACHE.add(TRACE, TRACE_ORDINAL),
+        CONNECT_BUFFER= CACHE.add(CONNECT, CONNECT_ORDINAL),
+        MOVE_BUFFER= CACHE.add(MOVE, MOVE_ORDINAL);
+
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpParser.java b/src/java/org/eclipse/jetty/http/HttpParser.java
new file mode 100644
index 0000000..f4471a3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpParser.java
@@ -0,0 +1,1279 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.io.bio.StreamEndPoint;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HttpParser implements Parser
+{
+    private static final Logger LOG = Log.getLogger(HttpParser.class);
+
+    // States
+    public static final int STATE_START=-14;
+    public static final int STATE_FIELD0=-13;
+    public static final int STATE_SPACE1=-12;
+    public static final int STATE_STATUS=-11;
+    public static final int STATE_URI=-10;
+    public static final int STATE_SPACE2=-9;
+    public static final int STATE_END0=-8;
+    public static final int STATE_END1=-7;
+    public static final int STATE_FIELD2=-6;
+    public static final int STATE_HEADER=-5;
+    public static final int STATE_HEADER_NAME=-4;
+    public static final int STATE_HEADER_IN_NAME=-3;
+    public static final int STATE_HEADER_VALUE=-2;
+    public static final int STATE_HEADER_IN_VALUE=-1;
+    public static final int STATE_END=0;
+    public static final int STATE_EOF_CONTENT=1;
+    public static final int STATE_CONTENT=2;
+    public static final int STATE_CHUNKED_CONTENT=3;
+    public static final int STATE_CHUNK_SIZE=4;
+    public static final int STATE_CHUNK_PARAMS=5;
+    public static final int STATE_CHUNK=6;
+    public static final int STATE_SEEKING_EOF=7;
+
+    private final EventHandler _handler;
+    private final Buffers _buffers; // source of buffers
+    private final EndPoint _endp;
+    private Buffer _header; // Buffer for header data (and small _content)
+    private Buffer _body; // Buffer for large content
+    private Buffer _buffer; // The current buffer in use (either _header or _content)
+    private CachedBuffer _cached;
+    private final View.CaseInsensitive _tok0; // Saved token: header name, request method or response version
+    private final View.CaseInsensitive _tok1; // Saved token: header value, request URI or response code
+    private String _multiLineValue;
+    private int _responseStatus; // If >0 then we are parsing a response
+    private boolean _forceContentBuffer;
+    private boolean _persistent;
+
+    /* ------------------------------------------------------------------------------- */
+    protected final View  _contentView=new View(); // View of the content in the buffer for {@link Input}
+    protected int _state=STATE_START;
+    protected byte _eol;
+    protected int _length;
+    protected long _contentLength;
+    protected long _contentPosition;
+    protected int _chunkLength;
+    protected int _chunkPosition;
+    private boolean _headResponse;
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Constructor.
+     */
+    public HttpParser(Buffer buffer, EventHandler handler)
+    {
+        _endp=null;
+        _buffers=null;
+        _header=buffer;
+        _buffer=buffer;
+        _handler=handler;
+
+        _tok0=new View.CaseInsensitive(_header);
+        _tok1=new View.CaseInsensitive(_header);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Constructor.
+     * @param buffers the buffers to use
+     * @param endp the endpoint
+     * @param handler the even handler
+     */
+    public HttpParser(Buffers buffers, EndPoint endp, EventHandler handler)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _handler=handler;
+        _tok0=new View.CaseInsensitive();
+        _tok1=new View.CaseInsensitive();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getContentRead()
+    {
+        return _contentPosition;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set if a HEAD response is expected
+     * @param head
+     */
+    public void setHeadResponse(boolean head)
+    {
+        _headResponse=head;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public int getState()
+    {
+        return _state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inContentState()
+    {
+        return _state > 0;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean inHeaderState()
+    {
+        return _state < 0;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isChunking()
+    {
+        return _contentLength==HttpTokens.CHUNKED_CONTENT;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return isState(STATE_START);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        return isState(STATE_END);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isMoreInBuffer()
+    throws IOException
+    {
+        return ( _header!=null && _header.hasContent() ||
+             _body!=null && _body.hasContent());
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isState(int state)
+    {
+        return _state == state;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean isPersistent()
+    {
+        return _persistent;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void setPersistent(boolean persistent)
+    {
+        _persistent = persistent;
+        if (!_persistent &&(_state==STATE_END || _state==STATE_START))
+            _state=STATE_SEEKING_EOF;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until {@link #STATE_END END} state.
+     * If the parser is already in the END state, then it is {@link #reset reset} and re-parsed.
+     * @throws IllegalStateException If the buffers have already been partially parsed.
+     */
+    public void parse() throws IOException
+    {
+        if (_state==STATE_END)
+            reset();
+        if (_state!=STATE_START)
+            throw new IllegalStateException("!START");
+
+        // continue parsing
+        while (_state != STATE_END)
+            if (parseNext()<0)
+                return;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until END state.
+     * This method will parse any remaining content in the current buffer as long as there is
+     * no unconsumed content. It does not care about the {@link #getState current state} of the parser.
+     * @see #parse
+     * @see #parseNext
+     */
+    public boolean parseAvailable() throws IOException
+    {
+        boolean progress=parseNext()>0;
+
+        // continue parsing
+        while (!isComplete() && _buffer!=null && _buffer.length()>0 && !_contentView.hasContent())
+        {
+            progress |= parseNext()>0;
+        }
+        return progress;
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Parse until next Event.
+     * @return an indication of progress <0 EOF, 0 no progress, >0 progress.
+     */
+    public int parseNext() throws IOException
+    {
+        try
+        {
+            int progress=0;
+
+            if (_state == STATE_END)
+                return 0;
+
+            if (_buffer==null)
+                _buffer=getHeaderBuffer();
+
+
+            if (_state == STATE_CONTENT && _contentPosition == _contentLength)
+            {
+                _state=STATE_END;
+                _handler.messageComplete(_contentPosition);
+                return 1;
+            }
+
+            int length=_buffer.length();
+
+            // Fill buffer if we can
+            if (length == 0)
+            {
+                int filled=-1;
+                IOException ex=null;
+                try
+                {
+                    filled=fill();
+                    LOG.debug("filled {}/{}",filled,_buffer.length());
+                }
+                catch(IOException e)
+                {
+                    LOG.debug(this.toString(),e);
+                    ex=e;
+                }
+
+                if (filled > 0 )
+                    progress++;
+                else if (filled < 0 )
+                {
+                    _persistent=false;
+
+                    // do we have content to deliver?
+                    if (_state>STATE_END)
+                    {
+                        if (_buffer.length()>0 && !_headResponse)
+                        {
+                            Buffer chunk=_buffer.get(_buffer.length());
+                            _contentPosition += chunk.length();
+                            _contentView.update(chunk);
+                            _handler.content(chunk); // May recurse here
+                        }
+                    }
+
+                    // was this unexpected?
+                    switch(_state)
+                    {
+                        case STATE_END:
+                        case STATE_SEEKING_EOF:
+                            _state=STATE_END;
+                            break;
+
+                        case STATE_EOF_CONTENT:
+                            _state=STATE_END;
+                            _handler.messageComplete(_contentPosition);
+                            break;
+
+                        default:
+                            _state=STATE_END;
+                            if (!_headResponse)
+                                _handler.earlyEOF();
+                            _handler.messageComplete(_contentPosition);
+                    }
+
+                    if (ex!=null)
+                        throw ex;
+
+                    if (!isComplete() && !isIdle())
+                        throw new EofException();
+
+                    return -1;
+                }
+                length=_buffer.length();
+            }
+
+
+            // Handle header states
+            byte ch;
+            byte[] array=_buffer.array();
+            int last=_state;
+            while (_state<STATE_END && length-->0)
+            {
+                if (last!=_state)
+                {
+                    progress++;
+                    last=_state;
+                }
+
+                ch=_buffer.get();
+
+                if (_eol == HttpTokens.CARRIAGE_RETURN)
+                {
+                    if (ch == HttpTokens.LINE_FEED)
+                    {
+                        _eol=HttpTokens.LINE_FEED;
+                        continue;
+                    }
+                    throw new HttpException(HttpStatus.BAD_REQUEST_400);
+                }
+                _eol=0;
+
+                switch (_state)
+                {
+                    case STATE_START:
+                        _contentLength=HttpTokens.UNKNOWN_CONTENT;
+                        _cached=null;
+                        if (ch > HttpTokens.SPACE || ch<0)
+                        {
+                            _buffer.mark();
+                            _state=STATE_FIELD0;
+                        }
+                        break;
+
+                    case STATE_FIELD0:
+                        if (ch == HttpTokens.SPACE)
+                        {
+                            _tok0.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+                            _responseStatus=HttpVersions.CACHE.get(_tok0)==null?-1:0;
+                            _state=STATE_SPACE1;
+                            continue;
+                        }
+                        else if (ch < HttpTokens.SPACE && ch>=0)
+                        {
+                            throw new HttpException(HttpStatus.BAD_REQUEST_400);
+                        }
+                        break;
+
+                    case STATE_SPACE1:
+                        if (ch > HttpTokens.SPACE || ch<0)
+                        {
+                            _buffer.mark();
+                            if (_responseStatus>=0)
+                            {
+                                _state=STATE_STATUS;
+                                _responseStatus=ch-'0';
+                            }
+                            else
+                                _state=STATE_URI;
+                        }
+                        else if (ch < HttpTokens.SPACE)
+                        {
+                            throw new HttpException(HttpStatus.BAD_REQUEST_400);
+                        }
+                        break;
+
+                    case STATE_STATUS:
+                        if (ch == HttpTokens.SPACE)
+                        {
+                            _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+                            _state=STATE_SPACE2;
+                            continue;
+                        }
+                        else if (ch>='0' && ch<='9')
+                        {
+                            _responseStatus=_responseStatus*10+(ch-'0');
+                            continue;
+                        }
+                        else if (ch < HttpTokens.SPACE && ch>=0)
+                        {
+                            _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
+                            _eol=ch;
+                            _state=STATE_HEADER;
+                            _tok0.setPutIndex(_tok0.getIndex());
+                            _tok1.setPutIndex(_tok1.getIndex());
+                            _multiLineValue=null;
+                            continue;
+                        }
+                        // not a digit, so must be a URI
+                        _state=STATE_URI;
+                        _responseStatus=-1;
+                        break;
+
+                    case STATE_URI:
+                        if (ch == HttpTokens.SPACE)
+                        {
+                            _tok1.update(_buffer.markIndex(), _buffer.getIndex() - 1);
+                            _state=STATE_SPACE2;
+                            continue;
+                        }
+                        else if (ch < HttpTokens.SPACE && ch>=0)
+                        {
+                            // HTTP/0.9
+                            _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _buffer.sliceFromMark(), null);
+                            _persistent=false;
+                            _state=STATE_SEEKING_EOF;
+                            _handler.headerComplete();
+                            _handler.messageComplete(_contentPosition);
+                            return 1;
+                        }
+                        break;
+
+                    case STATE_SPACE2:
+                        if (ch > HttpTokens.SPACE || ch<0)
+                        {
+                            _buffer.mark();
+                            _state=STATE_FIELD2;
+                        }
+                        else if (ch < HttpTokens.SPACE)
+                        {
+                            if (_responseStatus>0)
+                            {
+                                _handler.startResponse(HttpMethods.CACHE.lookup(_tok0), _responseStatus, null);
+                                _eol=ch;
+                                _state=STATE_HEADER;
+                                _tok0.setPutIndex(_tok0.getIndex());
+                                _tok1.setPutIndex(_tok1.getIndex());
+                                _multiLineValue=null;
+                            }
+                            else
+                            {
+                                // HTTP/0.9
+                                _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, null);
+                                _persistent=false;
+                                _state=STATE_SEEKING_EOF;
+                                _handler.headerComplete();
+                                _handler.messageComplete(_contentPosition);
+                                return 1;
+                            }
+                        }
+                        break;
+
+                    case STATE_FIELD2:
+                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        {
+                            Buffer version;
+                            if (_responseStatus>0)
+                                _handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,_buffer.sliceFromMark());
+                            else
+                                _handler.startRequest(HttpMethods.CACHE.lookup(_tok0), _tok1, version=HttpVersions.CACHE.lookup(_buffer.sliceFromMark()));
+                            _eol=ch;
+                            _persistent=HttpVersions.CACHE.getOrdinal(version)>=HttpVersions.HTTP_1_1_ORDINAL;
+                            _state=STATE_HEADER;
+                            _tok0.setPutIndex(_tok0.getIndex());
+                            _tok1.setPutIndex(_tok1.getIndex());
+                            _multiLineValue=null;
+                            continue;
+                        }
+                        break;
+
+                    case STATE_HEADER:
+                        switch(ch)
+                        {
+                            case HttpTokens.COLON:
+                            case HttpTokens.SPACE:
+                            case HttpTokens.TAB:
+                            {
+                                // header value without name - continuation?
+                                _length=-1;
+                                _state=STATE_HEADER_VALUE;
+                                break;
+                            }
+
+                            default:
+                            {
+                                // handler last header if any
+                                if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
+                                {
+                                    Buffer header=_cached!=null?_cached:HttpHeaders.CACHE.lookup(_tok0);
+                                    _cached=null;
+                                    Buffer value=_multiLineValue == null ? _tok1 : new ByteArrayBuffer(_multiLineValue);
+
+                                    int ho=HttpHeaders.CACHE.getOrdinal(header);
+                                    if (ho >= 0)
+                                    {
+                                        int vo;
+
+                                        switch (ho)
+                                        {
+                                            case HttpHeaders.CONTENT_LENGTH_ORDINAL:
+                                                if (_contentLength != HttpTokens.CHUNKED_CONTENT )
+                                                {
+                                                    try
+                                                    {
+                                                        _contentLength=BufferUtil.toLong(value);
+                                                    }
+                                                    catch(NumberFormatException e)
+                                                    {
+                                                        LOG.ignore(e);
+                                                        throw new HttpException(HttpStatus.BAD_REQUEST_400);
+                                                    }
+                                                    if (_contentLength <= 0)
+                                                        _contentLength=HttpTokens.NO_CONTENT;
+                                                }
+                                                break;
+
+                                            case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
+                                                value=HttpHeaderValues.CACHE.lookup(value);
+                                                vo=HttpHeaderValues.CACHE.getOrdinal(value);
+                                                if (HttpHeaderValues.CHUNKED_ORDINAL == vo)
+                                                    _contentLength=HttpTokens.CHUNKED_CONTENT;
+                                                else
+                                                {
+                                                    String c=value.toString(StringUtil.__ISO_8859_1);
+                                                    if (c.endsWith(HttpHeaderValues.CHUNKED))
+                                                        _contentLength=HttpTokens.CHUNKED_CONTENT;
+
+                                                    else if (c.indexOf(HttpHeaderValues.CHUNKED) >= 0)
+                                                        throw new HttpException(400,null);
+                                                }
+                                                break;
+
+                                            case HttpHeaders.CONNECTION_ORDINAL:
+                                                switch(HttpHeaderValues.CACHE.getOrdinal(value))
+                                                {
+                                                    case HttpHeaderValues.CLOSE_ORDINAL:
+                                                        _persistent=false;
+                                                        break;
+
+                                                    case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+                                                        _persistent=true;
+                                                        break;
+
+                                                    case -1: // No match, may be multi valued
+                                                    {
+                                                        for (String v : value.toString().split(","))
+                                                        {
+                                                            switch(HttpHeaderValues.CACHE.getOrdinal(v.trim()))
+                                                            {
+                                                                case HttpHeaderValues.CLOSE_ORDINAL:
+                                                                    _persistent=false;
+                                                                    break;
+
+                                                                case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+                                                                    _persistent=true;
+                                                                    break;
+                                                            }
+                                                        }
+                                                        break;
+                                                    }
+                                                }
+                                        }
+                                    }
+
+                                    _handler.parsedHeader(header, value);
+                                    _tok0.setPutIndex(_tok0.getIndex());
+                                    _tok1.setPutIndex(_tok1.getIndex());
+                                    _multiLineValue=null;
+                                }
+                                _buffer.setMarkIndex(-1);
+
+                                // now handle ch
+                                if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                                {
+                                    // is it a response that cannot have a body?
+                                    if (_responseStatus > 0  && // response  
+                                       (_responseStatus == 304  || // not-modified response
+                                        _responseStatus == 204 || // no-content response
+                                        _responseStatus < 200)) // 1xx response
+                                        _contentLength=HttpTokens.NO_CONTENT; // ignore any other headers set
+                                    // else if we don't know framing
+                                    else if (_contentLength == HttpTokens.UNKNOWN_CONTENT)
+                                    {
+                                        if (_responseStatus == 0  // request
+                                                || _responseStatus == 304 // not-modified response
+                                                || _responseStatus == 204 // no-content response
+                                                || _responseStatus < 200) // 1xx response
+                                            _contentLength=HttpTokens.NO_CONTENT;
+                                        else
+                                            _contentLength=HttpTokens.EOF_CONTENT;
+                                    }
+
+                                    _contentPosition=0;
+                                    _eol=ch;
+                                    if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
+                                        _eol=_buffer.get();
+
+                                    // We convert _contentLength to an int for this switch statement because
+                                    // we don't care about the amount of data available just whether there is some.
+                                    switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
+                                    {
+                                        case HttpTokens.EOF_CONTENT:
+                                            _state=STATE_EOF_CONTENT;
+                                            _handler.headerComplete(); // May recurse here !
+                                            break;
+
+                                        case HttpTokens.CHUNKED_CONTENT:
+                                            _state=STATE_CHUNKED_CONTENT;
+                                            _handler.headerComplete(); // May recurse here !
+                                            break;
+
+                                        case HttpTokens.NO_CONTENT:
+                                            _handler.headerComplete();
+                                            _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
+                                            _handler.messageComplete(_contentPosition);
+                                            return 1;
+
+                                        default:
+                                            _state=STATE_CONTENT;
+                                            _handler.headerComplete(); // May recurse here !
+                                            break;
+                                    }
+                                    return 1;
+                                }
+                                else
+                                {
+                                    // New header
+                                    _length=1;
+                                    _buffer.mark();
+                                    _state=STATE_HEADER_NAME;
+
+                                    // try cached name!
+                                    if (array!=null)
+                                    {
+                                        _cached=HttpHeaders.CACHE.getBest(array, _buffer.markIndex(), length+1);
+
+                                        if (_cached!=null)
+                                        {
+                                            _length=_cached.length();
+                                            _buffer.setGetIndex(_buffer.markIndex()+_length);
+                                            length=_buffer.length();
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        break;
+
+                    case STATE_HEADER_NAME:
+                        switch(ch)
+                        {
+                            case HttpTokens.CARRIAGE_RETURN:
+                            case HttpTokens.LINE_FEED:
+                                if (_length > 0)
+                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                _eol=ch;
+                                _state=STATE_HEADER;
+                                break;
+                            case HttpTokens.COLON:
+                                if (_length > 0 && _cached==null)
+                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                _length=-1;
+                                _state=STATE_HEADER_VALUE;
+                                break;
+                            case HttpTokens.SPACE:
+                            case HttpTokens.TAB:
+                                break;
+                            default:
+                            {
+                                _cached=null;
+                                if (_length == -1)
+                                    _buffer.mark();
+                                _length=_buffer.getIndex() - _buffer.markIndex();
+                                _state=STATE_HEADER_IN_NAME;
+                            }
+                        }
+
+                        break;
+
+                    case STATE_HEADER_IN_NAME:
+                        switch(ch)
+                        {
+                            case HttpTokens.CARRIAGE_RETURN:
+                            case HttpTokens.LINE_FEED:
+                                if (_length > 0)
+                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                _eol=ch;
+                                _state=STATE_HEADER;
+                                break;
+                            case HttpTokens.COLON:
+                                if (_length > 0 && _cached==null)
+                                    _tok0.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                _length=-1;
+                                _state=STATE_HEADER_VALUE;
+                                break;
+                            case HttpTokens.SPACE:
+                            case HttpTokens.TAB:
+                                _state=STATE_HEADER_NAME;
+                                break;
+                            default:
+                            {
+                                _cached=null;
+                                _length++;
+                            }
+                        }
+                        break;
+
+                    case STATE_HEADER_VALUE:
+                        switch(ch)
+                        {
+                            case HttpTokens.CARRIAGE_RETURN:
+                            case HttpTokens.LINE_FEED:
+                                if (_length > 0)
+                                {
+                                    if (_tok1.length() == 0)
+                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                    else
+                                    {
+                                        // Continuation line!
+                                        if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
+                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                        _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
+                                    }
+                                }
+                                _eol=ch;
+                                _state=STATE_HEADER;
+                                break;
+                            case HttpTokens.SPACE:
+                            case HttpTokens.TAB:
+                                break;
+                            default:
+                            {
+                                if (_length == -1)
+                                    _buffer.mark();
+                                _length=_buffer.getIndex() - _buffer.markIndex();
+                                _state=STATE_HEADER_IN_VALUE;
+                            }
+                        }
+                        break;
+
+                    case STATE_HEADER_IN_VALUE:
+                        switch(ch)
+                        {
+                            case HttpTokens.CARRIAGE_RETURN:
+                            case HttpTokens.LINE_FEED:
+                                if (_length > 0)
+                                {
+                                    if (_tok1.length() == 0)
+                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                    else
+                                    {
+                                        // Continuation line!
+                                        if (_multiLineValue == null) _multiLineValue=_tok1.toString(StringUtil.__ISO_8859_1);
+                                        _tok1.update(_buffer.markIndex(), _buffer.markIndex() + _length);
+                                        _multiLineValue += " " + _tok1.toString(StringUtil.__ISO_8859_1);
+                                    }
+                                }
+                                _eol=ch;
+                                _state=STATE_HEADER;
+                                break;
+                            case HttpTokens.SPACE:
+                            case HttpTokens.TAB:
+                                _state=STATE_HEADER_VALUE;
+                                break;
+                            default:
+                                _length++;
+                        }
+                        break;
+                }
+            } // end of HEADER states loop
+
+            // ==========================
+
+            // Handle HEAD response
+            if (_responseStatus>0 && _headResponse)
+            {
+                _state=_persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
+                _handler.messageComplete(_contentLength);
+            }
+
+
+            // ==========================
+
+            // Handle _content
+            length=_buffer.length();
+            Buffer chunk;
+            last=_state;
+            while (_state > STATE_END && length > 0)
+            {
+                if (last!=_state)
+                {
+                    progress++;
+                    last=_state;
+                }
+
+                if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer.peek() == HttpTokens.LINE_FEED)
+                {
+                    _eol=_buffer.get();
+                    length=_buffer.length();
+                    continue;
+                }
+                _eol=0;
+                switch (_state)
+                {
+                    case STATE_EOF_CONTENT:
+                        chunk=_buffer.get(_buffer.length());
+                        _contentPosition += chunk.length();
+                        _contentView.update(chunk);
+                        _handler.content(chunk); // May recurse here
+                        // TODO adjust the _buffer to keep unconsumed content
+                        return 1;
+
+                    case STATE_CONTENT:
+                    {
+                        long remaining=_contentLength - _contentPosition;
+                        if (remaining == 0)
+                        {
+                            _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+                            _handler.messageComplete(_contentPosition);
+                            return 1;
+                        }
+
+                        if (length > remaining)
+                        {
+                            // We can cast reamining to an int as we know that it is smaller than
+                            // or equal to length which is already an int.
+                            length=(int)remaining;
+                        }
+
+                        chunk=_buffer.get(length);
+                        _contentPosition += chunk.length();
+                        _contentView.update(chunk);
+                        _handler.content(chunk); // May recurse here
+
+                        if(_contentPosition == _contentLength)
+                        {
+                            _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+                            _handler.messageComplete(_contentPosition);
+                        }
+                        // TODO adjust the _buffer to keep unconsumed content
+                        return 1;
+                    }
+
+                    case STATE_CHUNKED_CONTENT:
+                    {
+                        ch=_buffer.peek();
+                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                            _eol=_buffer.get();
+                        else if (ch <= HttpTokens.SPACE)
+                            _buffer.get();
+                        else
+                        {
+                            _chunkLength=0;
+                            _chunkPosition=0;
+                            _state=STATE_CHUNK_SIZE;
+                        }
+                        break;
+                    }
+
+                    case STATE_CHUNK_SIZE:
+                    {
+                        ch=_buffer.get();
+                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        {
+                            _eol=ch;
+
+                            if (_chunkLength == 0)
+                            {
+                                if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
+                                    _eol=_buffer.get();
+                                _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+                                _handler.messageComplete(_contentPosition);
+                                return 1;
+                            }
+                            else
+                                _state=STATE_CHUNK;
+                        }
+                        else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+                            _state=STATE_CHUNK_PARAMS;
+                        else if (ch >= '0' && ch <= '9')
+                            _chunkLength=_chunkLength * 16 + (ch - '0');
+                        else if (ch >= 'a' && ch <= 'f')
+                            _chunkLength=_chunkLength * 16 + (10 + ch - 'a');
+                        else if (ch >= 'A' && ch <= 'F')
+                            _chunkLength=_chunkLength * 16 + (10 + ch - 'A');
+                        else
+                            throw new IOException("bad chunk char: " + ch);
+                        break;
+                    }
+
+                    case STATE_CHUNK_PARAMS:
+                    {
+                        ch=_buffer.get();
+                        if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
+                        {
+                            _eol=ch;
+                            if (_chunkLength == 0)
+                            {
+                                if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasContent() && _buffer.peek()==HttpTokens.LINE_FEED)
+                                    _eol=_buffer.get();
+                                _state=_persistent?STATE_END:STATE_SEEKING_EOF;
+                                _handler.messageComplete(_contentPosition);
+                                return 1;
+                            }
+                            else
+                                _state=STATE_CHUNK;
+                        }
+                        break;
+                    }
+
+                    case STATE_CHUNK:
+                    {
+                        int remaining=_chunkLength - _chunkPosition;
+                        if (remaining == 0)
+                        {
+                            _state=STATE_CHUNKED_CONTENT;
+                            break;
+                        }
+                        else if (length > remaining)
+                            length=remaining;
+                        chunk=_buffer.get(length);
+                        _contentPosition += chunk.length();
+                        _chunkPosition += chunk.length();
+                        _contentView.update(chunk);
+                        _handler.content(chunk); // May recurse here
+                        // TODO adjust the _buffer to keep unconsumed content
+                        return 1;
+                    }
+
+                    case STATE_SEEKING_EOF:
+                    {                        
+                        // Close if there is more data than CRLF
+                        if (_buffer.length()>2)
+                        {
+                            _state=STATE_END;
+                            _endp.close();
+                        }
+                        else  
+                        {
+                            // or if the data is not white space
+                            while (_buffer.length()>0)
+                                if (!Character.isWhitespace(_buffer.get()))
+                                {
+                                    _state=STATE_END;
+                                    _endp.close();
+                                    _buffer.clear();
+                                }
+                        }
+                        
+                        _buffer.clear();
+                        break;
+                    }
+                }
+
+                length=_buffer.length();
+            }
+
+            return progress;
+        }
+        catch(HttpException e)
+        {
+            _persistent=false;
+            _state=STATE_SEEKING_EOF;
+            throw e;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /** fill the buffers from the endpoint
+     *
+     */
+    protected int fill() throws IOException
+    {
+        // Do we have a buffer?
+        if (_buffer==null)
+            _buffer=getHeaderBuffer();
+
+        // Is there unconsumed content in body buffer
+        if (_state>STATE_END && _buffer==_header && _header!=null && !_header.hasContent() && _body!=null && _body.hasContent())
+        {
+            _buffer=_body;
+            return _buffer.length();
+        }
+
+        // Shall we switch to a body buffer?
+        if (_buffer==_header && _state>STATE_END && _header.length()==0 && (_forceContentBuffer || (_contentLength-_contentPosition)>_header.capacity()) && (_body!=null||_buffers!=null))
+        {
+            if (_body==null)
+                _body=_buffers.getBuffer();
+            _buffer=_body;
+        }
+
+        // Do we have somewhere to fill from?
+        if (_endp != null )
+        {
+            // Shall we compact the body?
+            if (_buffer==_body || _state>STATE_END)
+            {
+                _buffer.compact();
+            }
+
+            // Are we full?
+            if (_buffer.space() == 0)
+            {
+                LOG.warn("HttpParser Full for {} ",_endp);
+                _buffer.clear();
+                throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large: "+(_buffer==_body?"body":"head"));
+            }
+
+            try
+            {
+                int filled = _endp.fill(_buffer);
+                return filled;
+            }
+            catch(IOException e)
+            {
+                LOG.debug(e);
+                throw (e instanceof EofException) ? e:new EofException(e);
+            }
+        }
+
+        return -1;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void reset()
+    {
+        // reset state
+        _contentView.setGetIndex(_contentView.putIndex());
+        _state=_persistent?STATE_START:(_endp.isInputShutdown()?STATE_END:STATE_SEEKING_EOF);
+        _contentLength=HttpTokens.UNKNOWN_CONTENT;
+        _contentPosition=0;
+        _length=0;
+        _responseStatus=0;
+
+        // Consume LF if CRLF
+        if (_eol == HttpTokens.CARRIAGE_RETURN && _buffer!=null && _buffer.hasContent() && _buffer.peek() == HttpTokens.LINE_FEED)
+            _eol=_buffer.get();
+
+        if (_body!=null && _body.hasContent())
+        {
+            // There is content in the body after the end of the request.
+            // This is probably a pipelined header of the next request, so we need to
+            // copy it to the header buffer.
+            if (_header==null)
+                getHeaderBuffer();
+            else
+            {
+                _header.setMarkIndex(-1);
+                _header.compact();
+            }
+            int take=_header.space();
+            if (take>_body.length())
+                take=_body.length();
+            _body.peek(_body.getIndex(),take);
+            _body.skip(_header.put(_body.peek(_body.getIndex(),take)));
+        }
+
+        if (_header!=null)
+        {
+            _header.setMarkIndex(-1);
+            _header.compact();
+        }
+        if (_body!=null)
+            _body.setMarkIndex(-1);
+
+        _buffer=_header;
+        returnBuffers();
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    public void returnBuffers()
+    {
+        if (_body!=null && !_body.hasContent() && _body.markIndex()==-1 && _buffers!=null)
+        {
+            if (_buffer==_body)
+                _buffer=_header;
+            if (_buffers!=null)
+                _buffers.returnBuffer(_body);
+            _body=null;
+        }
+
+        if (_header!=null && !_header.hasContent() && _header.markIndex()==-1 && _buffers!=null)
+        {
+            if (_buffer==_header)
+                _buffer=null;
+            _buffers.returnBuffer(_header);
+            _header=null;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public void setState(int state)
+    {
+        this._state=state;
+        _contentLength=HttpTokens.UNKNOWN_CONTENT;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public String toString(Buffer buf)
+    {
+        return "state=" + _state + " length=" + _length + " buf=" + buf.hashCode();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return String.format("%s{s=%d,l=%d,c=%d}",
+                getClass().getSimpleName(),
+                _state,
+                _length,
+                _contentLength);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getHeaderBuffer()
+    {
+        if (_header == null)
+        {
+            _header=_buffers.getHeader();
+            _tok0.update(_header);
+            _tok1.update(_header);
+        }
+        return _header;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBodyBuffer()
+    {
+        return _body;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param force True if a new buffer will be forced to be used for content and the header buffer will not be used.
+     */
+    public void setForceContentBuffer(boolean force)
+    {
+        _forceContentBuffer=force;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer blockForContent(long maxIdleTime) throws IOException
+    {
+        if (_contentView.length()>0)
+            return _contentView;
+
+        if (getState() <= STATE_END || isState(STATE_SEEKING_EOF))
+            return null;
+
+        try
+        {
+            parseNext();
+
+            // parse until some progress is made (or IOException thrown for timeout)
+            while(_contentView.length() == 0 && !(isState(HttpParser.STATE_END)||isState(HttpParser.STATE_SEEKING_EOF)) && _endp!=null && _endp.isOpen())
+            {
+                if (!_endp.isBlocking())
+                {
+                    if (parseNext()>0)
+                        continue;
+
+                    if (!_endp.blockReadable(maxIdleTime))
+                    {
+                        _endp.close();
+                        throw new EofException("timeout");
+                    }
+                }
+
+                parseNext();
+            }
+        }
+        catch(IOException e)
+        {
+            // TODO is this needed?
+            _endp.close();
+            throw e;
+        }
+
+        return _contentView.length()>0?_contentView:null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see java.io.InputStream#available()
+     */
+    public int available() throws IOException
+    {
+        if (_contentView!=null && _contentView.length()>0)
+            return _contentView.length();
+
+        if (_endp.isBlocking())
+        {
+            if (_state>0 && _endp instanceof StreamEndPoint)
+                return ((StreamEndPoint)_endp).getInputStream().available()>0?1:0;
+
+            return 0;
+        }
+
+        parseNext();
+        return _contentView==null?0:_contentView.length();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static abstract class EventHandler
+    {
+        public abstract void content(Buffer ref) throws IOException;
+
+        public void headerComplete() throws IOException
+        {
+        }
+
+        public void messageComplete(long contentLength) throws IOException
+        {
+        }
+
+        /**
+         * This is the method called by parser when a HTTP Header name and value is found
+         */
+        public void parsedHeader(Buffer name, Buffer value) throws IOException
+        {
+        }
+
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         */
+        public abstract void startRequest(Buffer method, Buffer url, Buffer version)
+                throws IOException;
+
+        /**
+         * This is the method called by parser when the HTTP request line is parsed
+         */
+        public abstract void startResponse(Buffer version, int status, Buffer reason)
+                throws IOException;
+
+        public void earlyEOF()
+        {}
+    }
+
+
+
+
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpSchemes.java b/src/java/org/eclipse/jetty/http/HttpSchemes.java
new file mode 100644
index 0000000..ceef394
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpSchemes.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * 
+ * 
+ */
+public class HttpSchemes
+{
+    public final static String
+        HTTP ="http",
+        HTTPS="https";
+    
+    public final static Buffer
+        HTTP_BUFFER = new ByteArrayBuffer(HTTP),
+        HTTPS_BUFFER = new ByteArrayBuffer(HTTPS);
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpStatus.java b/src/java/org/eclipse/jetty/http/HttpStatus.java
new file mode 100644
index 0000000..7db1fa6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpStatus.java
@@ -0,0 +1,1036 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * <p>
+ * HttpStatusCode enum class, for status codes based on various HTTP RFCs. (see
+ * table below)
+ * </p>
+ *
+ * <table border="1" cellpadding="5">
+ * <tr>
+ * <th>Enum</th>
+ * <th>Code</th>
+ * <th>Message</th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a></th>
+ * <th>
+ * <a href="http://tools.ietf.org/html/rfc2518">RFC 2518 - WEBDAV</a></th>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Informational - 1xx</code></strong></td>
+ * <td colspan="5">{@link #isInformational(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #CONTINUE_100}</td>
+ * <td>100</td>
+ * <td>Continue</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.1">Sec. 10.1.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SWITCHING_PROTOCOLS_101}</td>
+ * <td>101</td>
+ * <td>Switching Protocols</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.1.2">Sec. 10.1.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROCESSING_102}</td>
+ * <td>102</td>
+ * <td>Processing</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.1">Sec. 10.1</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Success - 2xx</code></strong></td>
+ * <td colspan="5">{@link #isSuccess(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #OK_200}</td>
+ * <td>200</td>
+ * <td>OK</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.1">Sec. 10.2.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CREATED_201}</td>
+ * <td>201</td>
+ * <td>Created</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.2">Sec. 10.2.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #ACCEPTED_202}</td>
+ * <td>202</td>
+ * <td>Accepted</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.3">Sec. 10.2.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NON_AUTHORITATIVE_INFORMATION_203}</td>
+ * <td>203</td>
+ * <td>Non Authoritative Information</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.4">Sec. 10.2.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NO_CONTENT_204}</td>
+ * <td>204</td>
+ * <td>No Content</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.2">Sec. 9.2</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.5">Sec. 10.2.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #RESET_CONTENT_205}</td>
+ * <td>205</td>
+ * <td>Reset Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.6">Sec. 10.2.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PARTIAL_CONTENT_206}</td>
+ * <td>206</td>
+ * <td>Partial Content</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.2.7">Sec. 10.2.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MULTI_STATUS_207}</td>
+ * <td>207</td>
+ * <td>Multi-Status</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.2">Sec. 10.2</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>207</strike></td>
+ * <td><strike>Partial Update OK</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-rev-01.txt"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Redirection - 3xx</code></strong></td>
+ * <td colspan="5">{@link #isRedirection(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #MULTIPLE_CHOICES_300}</td>
+ * <td>300</td>
+ * <td>Multiple Choices</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.1">Sec. 10.3.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_PERMANENTLY_301}</td>
+ * <td>301</td>
+ * <td>Moved Permanently</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.2">Sec. 10.3.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #MOVED_TEMPORARILY_302}</td>
+ * <td>302</td>
+ * <td>Moved Temporarily</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>(now "<code>302 Found</code>")</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FOUND_302}</td>
+ * <td>302</td>
+ * <td>Found</td>
+ * <td>(was "<code>302 Moved Temporarily</code>")</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.3">Sec. 10.3.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SEE_OTHER_303}</td>
+ * <td>303</td>
+ * <td>See Other</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.4">Sec. 10.3.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_MODIFIED_304}</td>
+ * <td>304</td>
+ * <td>Not Modified</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.3">Sec. 9.3</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.5">Sec. 10.3.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #USE_PROXY_305}</td>
+ * <td>305</td>
+ * <td>Use Proxy</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.6">Sec. 10.3.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>306</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.7">Sec. 10.3.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #TEMPORARY_REDIRECT_307}</td>
+ * <td>307</td>
+ * <td>Temporary Redirect</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.3.8">Sec. 10.3.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Client Error - 4xx</code></strong></td>
+ * <td colspan="5">{@link #isClientError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #BAD_REQUEST_400}</td>
+ * <td>400</td>
+ * <td>Bad Request</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.1">Sec. 10.4.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNAUTHORIZED_401}</td>
+ * <td>401</td>
+ * <td>Unauthorized</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.2">Sec. 10.4.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PAYMENT_REQUIRED_402}</td>
+ * <td>402</td>
+ * <td>Payment Required</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.3">Sec. 10.4.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FORBIDDEN_403}</td>
+ * <td>403</td>
+ * <td>Forbidden</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.4">Sec. 10.4.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_FOUND_404}</td>
+ * <td>404</td>
+ * <td>Not Found</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.4">Sec. 9.4</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.5">Sec. 10.4.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #METHOD_NOT_ALLOWED_405}</td>
+ * <td>405</td>
+ * <td>Method Not Allowed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.6">Sec. 10.4.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_ACCEPTABLE_406}</td>
+ * <td>406</td>
+ * <td>Not Acceptable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.7">Sec. 10.4.7</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PROXY_AUTHENTICATION_REQUIRED_407}</td>
+ * <td>407</td>
+ * <td>Proxy Authentication Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.8">Sec. 10.4.8</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_TIMEOUT_408}</td>
+ * <td>408</td>
+ * <td>Request Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.9">Sec. 10.4.9</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #CONFLICT_409}</td>
+ * <td>409</td>
+ * <td>Conflict</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.10">Sec. 10.4.10</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GONE_410}</td>
+ * <td>410</td>
+ * <td>Gone</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.11">Sec. 10.4.11</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LENGTH_REQUIRED_411}</td>
+ * <td>411</td>
+ * <td>Length Required</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.12">Sec. 10.4.12</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #PRECONDITION_FAILED_412}</td>
+ * <td>412</td>
+ * <td>Precondition Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.13">Sec. 10.4.13</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_ENTITY_TOO_LARGE_413}</td>
+ * <td>413</td>
+ * <td>Request Entity Too Large</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.14">Sec. 10.4.14</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUEST_URI_TOO_LONG_414}</td>
+ * <td>414</td>
+ * <td>Request-URI Too Long</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.15">Sec. 10.4.15</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNSUPPORTED_MEDIA_TYPE_415}</td>
+ * <td>415</td>
+ * <td>Unsupported Media Type</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.16">Sec. 10.4.16</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #REQUESTED_RANGE_NOT_SATISFIABLE_416}</td>
+ * <td>416</td>
+ * <td>Requested Range Not Satisfiable</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.17">Sec. 10.4.17</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #EXPECTATION_FAILED_417}</td>
+ * <td>417</td>
+ * <td>Expectation Failed</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.4.18">Sec. 10.4.18</a>
+ * </td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Reauthentication Required</strike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.19"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>418</strike></td>
+ * <td><strike>Unprocessable Entity</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.3"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Proxy Reauthentication Required</stike></td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-http-v11-spec-rev-01#section-10.4.20"
+ * >draft/01</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>419</strike></td>
+ * <td><strike>Insufficient Space on Resource</stike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.4"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td><strike>420</strike></td>
+ * <td><strike>Method Failure</strike></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href=
+ * "http://tools.ietf.org/html/draft-ietf-webdav-protocol-05#section-10.5"
+ * >draft/05</a></td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>421</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #UNPROCESSABLE_ENTITY_422}</td>
+ * <td>422</td>
+ * <td>Unprocessable Entity</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.3">Sec. 10.3</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #LOCKED_423}</td>
+ * <td>423</td>
+ * <td>Locked</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.4">Sec. 10.4</a></td>
+ * </tr>
+ * <tr>
+ * <td>{@link #FAILED_DEPENDENCY_424}</td>
+ * <td>424</td>
+ * <td>Failed Dependency</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.5">Sec. 10.5</a></td>
+ * </tr>
+ *
+ * <tr>
+ * <td><strong><code>Server Error - 5xx</code></strong></td>
+ * <td colspan="5">{@link #isServerError(int)}</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>{@link #INTERNAL_SERVER_ERROR_500}</td>
+ * <td>500</td>
+ * <td>Internal Server Error</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.1">Sec. 10.5.1</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #NOT_IMPLEMENTED_501}</td>
+ * <td>501</td>
+ * <td>Not Implemented</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.2">Sec. 10.5.2</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #BAD_GATEWAY_502}</td>
+ * <td>502</td>
+ * <td>Bad Gateway</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.3">Sec. 10.5.3</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #SERVICE_UNAVAILABLE_503}</td>
+ * <td>503</td>
+ * <td>Service Unavailable</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc1945#section-9.5">Sec. 9.5</a></td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.4">Sec. 10.5.4</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #GATEWAY_TIMEOUT_504}</td>
+ * <td>504</td>
+ * <td>Gateway Timeout</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.5">Sec. 10.5.5</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #HTTP_VERSION_NOT_SUPPORTED_505}</td>
+ * <td>505</td>
+ * <td>HTTP Version Not Supported</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2616#section-10.5.6">Sec. 10.5.6</a></td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>&nbsp;</td>
+ * <td>506</td>
+ * <td><em>(Unused)</em></td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * </tr>
+ * <tr>
+ * <td>{@link #INSUFFICIENT_STORAGE_507}</td>
+ * <td>507</td>
+ * <td>Insufficient Storage</td>
+ * <td>&nbsp;</td>
+ * <td>&nbsp;</td>
+ * <td>
+ * <a href="http://tools.ietf.org/html/rfc2518#section-10.6">Sec. 10.6</a></td>
+ * </tr>
+ *
+ * </table>
+ *
+ * @version $Id$
+ */
+public class HttpStatus
+{
+    public final static int CONTINUE_100 = 100;
+    public final static int SWITCHING_PROTOCOLS_101 = 101;
+    public final static int PROCESSING_102 = 102;
+
+    public final static int OK_200 = 200;
+    public final static int CREATED_201 = 201;
+    public final static int ACCEPTED_202 = 202;
+    public final static int NON_AUTHORITATIVE_INFORMATION_203 = 203;
+    public final static int NO_CONTENT_204 = 204;
+    public final static int RESET_CONTENT_205 = 205;
+    public final static int PARTIAL_CONTENT_206 = 206;
+    public final static int MULTI_STATUS_207 = 207;
+
+    public final static int MULTIPLE_CHOICES_300 = 300;
+    public final static int MOVED_PERMANENTLY_301 = 301;
+    public final static int MOVED_TEMPORARILY_302 = 302;
+    public final static int FOUND_302 = 302;
+    public final static int SEE_OTHER_303 = 303;
+    public final static int NOT_MODIFIED_304 = 304;
+    public final static int USE_PROXY_305 = 305;
+    public final static int TEMPORARY_REDIRECT_307 = 307;
+
+    public final static int BAD_REQUEST_400 = 400;
+    public final static int UNAUTHORIZED_401 = 401;
+    public final static int PAYMENT_REQUIRED_402 = 402;
+    public final static int FORBIDDEN_403 = 403;
+    public final static int NOT_FOUND_404 = 404;
+    public final static int METHOD_NOT_ALLOWED_405 = 405;
+    public final static int NOT_ACCEPTABLE_406 = 406;
+    public final static int PROXY_AUTHENTICATION_REQUIRED_407 = 407;
+    public final static int REQUEST_TIMEOUT_408 = 408;
+    public final static int CONFLICT_409 = 409;
+    public final static int GONE_410 = 410;
+    public final static int LENGTH_REQUIRED_411 = 411;
+    public final static int PRECONDITION_FAILED_412 = 412;
+    public final static int REQUEST_ENTITY_TOO_LARGE_413 = 413;
+    public final static int REQUEST_URI_TOO_LONG_414 = 414;
+    public final static int UNSUPPORTED_MEDIA_TYPE_415 = 415;
+    public final static int REQUESTED_RANGE_NOT_SATISFIABLE_416 = 416;
+    public final static int EXPECTATION_FAILED_417 = 417;
+    public final static int UNPROCESSABLE_ENTITY_422 = 422;
+    public final static int LOCKED_423 = 423;
+    public final static int FAILED_DEPENDENCY_424 = 424;
+
+    public final static int INTERNAL_SERVER_ERROR_500 = 500;
+    public final static int NOT_IMPLEMENTED_501 = 501;
+    public final static int BAD_GATEWAY_502 = 502;
+    public final static int SERVICE_UNAVAILABLE_503 = 503;
+    public final static int GATEWAY_TIMEOUT_504 = 504;
+    public final static int HTTP_VERSION_NOT_SUPPORTED_505 = 505;
+    public final static int INSUFFICIENT_STORAGE_507 = 507;
+
+    public static final int MAX_CODE = 507;
+
+
+    private static final Code[] codeMap = new Code[MAX_CODE+1];
+
+    static
+    {
+        for (Code code : Code.values())
+        {
+            codeMap[code._code] = code;
+        }
+    }
+
+
+    public enum Code
+    {
+        /*
+         * --------------------------------------------------------------------
+         * Informational messages in 1xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>100 Continue</code> */
+        CONTINUE(CONTINUE_100, "Continue"),
+        /** <code>101 Switching Protocols</code> */
+        SWITCHING_PROTOCOLS(SWITCHING_PROTOCOLS_101, "Switching Protocols"),
+        /** <code>102 Processing</code> */
+        PROCESSING(PROCESSING_102, "Processing"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Success messages in 2xx series. As defined by ... RFC 1945 - HTTP/1.0
+         * RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>200 OK</code> */
+        OK(OK_200, "OK"),
+        /** <code>201 Created</code> */
+        CREATED(CREATED_201, "Created"),
+        /** <code>202 Accepted</code> */
+        ACCEPTED(ACCEPTED_202, "Accepted"),
+        /** <code>203 Non Authoritative Information</code> */
+        NON_AUTHORITATIVE_INFORMATION(NON_AUTHORITATIVE_INFORMATION_203, "Non Authoritative Information"),
+        /** <code>204 No Content</code> */
+        NO_CONTENT(NO_CONTENT_204, "No Content"),
+        /** <code>205 Reset Content</code> */
+        RESET_CONTENT(RESET_CONTENT_205, "Reset Content"),
+        /** <code>206 Partial Content</code> */
+        PARTIAL_CONTENT(PARTIAL_CONTENT_206, "Partial Content"),
+        /** <code>207 Multi-Status</code> */
+        MULTI_STATUS(MULTI_STATUS_207, "Multi-Status"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Redirection messages in 3xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1
+         */
+
+        /** <code>300 Mutliple Choices</code> */
+        MULTIPLE_CHOICES(MULTIPLE_CHOICES_300, "Multiple Choices"),
+        /** <code>301 Moved Permanently</code> */
+        MOVED_PERMANENTLY(MOVED_PERMANENTLY_301, "Moved Permanently"),
+        /** <code>302 Moved Temporarily</code> */
+        MOVED_TEMPORARILY(MOVED_TEMPORARILY_302, "Moved Temporarily"),
+        /** <code>302 Found</code> */
+        FOUND(FOUND_302, "Found"),
+        /** <code>303 See Other</code> */
+        SEE_OTHER(SEE_OTHER_303, "See Other"),
+        /** <code>304 Not Modified</code> */
+        NOT_MODIFIED(NOT_MODIFIED_304, "Not Modified"),
+        /** <code>305 Use Proxy</code> */
+        USE_PROXY(USE_PROXY_305, "Use Proxy"),
+        /** <code>307 Temporary Redirect</code> */
+        TEMPORARY_REDIRECT(TEMPORARY_REDIRECT_307, "Temporary Redirect"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Client Error messages in 4xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>400 Bad Request</code> */
+        BAD_REQUEST(BAD_REQUEST_400, "Bad Request"),
+        /** <code>401 Unauthorized</code> */
+        UNAUTHORIZED(UNAUTHORIZED_401, "Unauthorized"),
+        /** <code>402 Payment Required</code> */
+        PAYMENT_REQUIRED(PAYMENT_REQUIRED_402, "Payment Required"),
+        /** <code>403 Forbidden</code> */
+        FORBIDDEN(FORBIDDEN_403, "Forbidden"),
+        /** <code>404 Not Found</code> */
+        NOT_FOUND(NOT_FOUND_404, "Not Found"),
+        /** <code>405 Method Not Allowed</code> */
+        METHOD_NOT_ALLOWED(METHOD_NOT_ALLOWED_405, "Method Not Allowed"),
+        /** <code>406 Not Acceptable</code> */
+        NOT_ACCEPTABLE(NOT_ACCEPTABLE_406, "Not Acceptable"),
+        /** <code>407 Proxy Authentication Required</code> */
+        PROXY_AUTHENTICATION_REQUIRED(PROXY_AUTHENTICATION_REQUIRED_407, "Proxy Authentication Required"),
+        /** <code>408 Request Timeout</code> */
+        REQUEST_TIMEOUT(REQUEST_TIMEOUT_408, "Request Timeout"),
+        /** <code>409 Conflict</code> */
+        CONFLICT(CONFLICT_409, "Conflict"),
+        /** <code>410 Gone</code> */
+        GONE(GONE_410, "Gone"),
+        /** <code>411 Length Required</code> */
+        LENGTH_REQUIRED(LENGTH_REQUIRED_411, "Length Required"),
+        /** <code>412 Precondition Failed</code> */
+        PRECONDITION_FAILED(PRECONDITION_FAILED_412, "Precondition Failed"),
+        /** <code>413 Request Entity Too Large</code> */
+        REQUEST_ENTITY_TOO_LARGE(REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large"),
+        /** <code>414 Request-URI Too Long</code> */
+        REQUEST_URI_TOO_LONG(REQUEST_URI_TOO_LONG_414, "Request-URI Too Long"),
+        /** <code>415 Unsupported Media Type</code> */
+        UNSUPPORTED_MEDIA_TYPE(UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Media Type"),
+        /** <code>416 Requested Range Not Satisfiable</code> */
+        REQUESTED_RANGE_NOT_SATISFIABLE(REQUESTED_RANGE_NOT_SATISFIABLE_416, "Requested Range Not Satisfiable"),
+        /** <code>417 Expectation Failed</code> */
+        EXPECTATION_FAILED(EXPECTATION_FAILED_417, "Expectation Failed"),
+        /** <code>422 Unprocessable Entity</code> */
+        UNPROCESSABLE_ENTITY(UNPROCESSABLE_ENTITY_422, "Unprocessable Entity"),
+        /** <code>423 Locked</code> */
+        LOCKED(LOCKED_423, "Locked"),
+        /** <code>424 Failed Dependency</code> */
+        FAILED_DEPENDENCY(FAILED_DEPENDENCY_424, "Failed Dependency"),
+
+        /*
+         * --------------------------------------------------------------------
+         * Server Error messages in 5xx series. As defined by ... RFC 1945 -
+         * HTTP/1.0 RFC 2616 - HTTP/1.1 RFC 2518 - WebDAV
+         */
+
+        /** <code>500 Server Error</code> */
+        INTERNAL_SERVER_ERROR(INTERNAL_SERVER_ERROR_500, "Server Error"),
+        /** <code>501 Not Implemented</code> */
+        NOT_IMPLEMENTED(NOT_IMPLEMENTED_501, "Not Implemented"),
+        /** <code>502 Bad Gateway</code> */
+        BAD_GATEWAY(BAD_GATEWAY_502, "Bad Gateway"),
+        /** <code>503 Service Unavailable</code> */
+        SERVICE_UNAVAILABLE(SERVICE_UNAVAILABLE_503, "Service Unavailable"),
+        /** <code>504 Gateway Timeout</code> */
+        GATEWAY_TIMEOUT(GATEWAY_TIMEOUT_504, "Gateway Timeout"),
+        /** <code>505 HTTP Version Not Supported</code> */
+        HTTP_VERSION_NOT_SUPPORTED(HTTP_VERSION_NOT_SUPPORTED_505, "HTTP Version Not Supported"),
+        /** <code>507 Insufficient Storage</code> */
+        INSUFFICIENT_STORAGE(INSUFFICIENT_STORAGE_507, "Insufficient Storage");
+
+        private final int _code;
+        private final String _message;
+
+        private Code(int code, String message)
+        {
+            this._code = code;
+            _message=message;
+        }
+
+        public int getCode()
+        {
+            return _code;
+        }
+
+        public String getMessage()
+        {
+            return _message;
+        }
+
+
+        public boolean equals(int code)
+        {
+            return (this._code == code);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("[%03d %s]",this._code,this.getMessage());
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Informational</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Informational</code> messages.
+         */
+        public boolean isInformational()
+        {
+            return HttpStatus.isInformational(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Success</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Success</code> messages.
+         */
+        public boolean isSuccess()
+        {
+            return HttpStatus.isSuccess(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Redirection</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Redirection</code> messages.
+         */
+        public boolean isRedirection()
+        {
+            return HttpStatus.isRedirection(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Client Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Client Error</code> messages.
+         */
+        public boolean isClientError()
+        {
+            return HttpStatus.isClientError(this._code);
+        }
+
+        /**
+         * Simple test against an code to determine if it falls into the
+         * <code>Server Error</code> message category as defined in the <a
+         * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>,
+         * and <a href="http://tools.ietf.org/html/rfc2616">RFC 2616 -
+         * HTTP/1.1</a>.
+         *
+         * @return true if within range of codes that belongs to
+         *         <code>Server Error</code> messages.
+         */
+        public boolean isServerError()
+        {
+            return HttpStatus.isServerError(this._code);
+        }
+    }
+
+
+    /**
+     * Get the HttpStatusCode for a specific code
+     *
+     * @param code
+     *            the code to lookup.
+     * @return the {@link HttpStatus} if found, or null if not found.
+     */
+    public static Code getCode(int code)
+    {
+        if (code <= MAX_CODE)
+        {
+            return codeMap[code];
+        }
+        return null;
+    }
+
+    /**
+     * Get the status message for a specific code.
+     *
+     * @param code
+     *            the code to look up
+     * @return the specific message, or the code number itself if code
+     *         does not match known list.
+     */
+    public static String getMessage(int code)
+    {
+        Code codeEnum = getCode(code);
+        if (codeEnum != null)
+        {
+            return codeEnum.getMessage();
+        }
+        else
+        {
+            return Integer.toString(code);
+        }
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Informational</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Informational</code> messages.
+     */
+    public static boolean isInformational(int code)
+    {
+        return ((100 <= code) && (code <= 199));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Success</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Success</code> messages.
+     */
+    public static boolean isSuccess(int code)
+    {
+        return ((200 <= code) && (code <= 299));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Redirection</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Redirection</code> messages.
+     */
+    public static boolean isRedirection(int code)
+    {
+        return ((300 <= code) && (code <= 399));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Client Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Client Error</code> messages.
+     */
+    public static boolean isClientError(int code)
+    {
+        return ((400 <= code) && (code <= 499));
+    }
+
+    /**
+     * Simple test against an code to determine if it falls into the
+     * <code>Server Error</code> message category as defined in the <a
+     * href="http://tools.ietf.org/html/rfc1945">RFC 1945 - HTTP/1.0</a>, and <a
+     * href="http://tools.ietf.org/html/rfc2616">RFC 2616 - HTTP/1.1</a>.
+     *
+     * @param code
+     *            the code to test.
+     * @return true if within range of codes that belongs to
+     *         <code>Server Error</code> messages.
+     */
+    public static boolean isServerError(int code)
+    {
+        return ((500 <= code) && (code <= 599));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpTokens.java b/src/java/org/eclipse/jetty/http/HttpTokens.java
new file mode 100644
index 0000000..fce759c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpTokens.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+/**
+ * HTTP constants
+ */
+public interface HttpTokens
+{
+    // Terminal symbols.
+    static final byte COLON= (byte)':';
+    static final byte SPACE= 0x20;
+    static final byte CARRIAGE_RETURN= 0x0D;
+    static final byte LINE_FEED= 0x0A;
+    static final byte[] CRLF = {CARRIAGE_RETURN,LINE_FEED};
+    static final byte SEMI_COLON= (byte)';';
+    static final byte TAB= 0x09;
+
+    public static final int SELF_DEFINING_CONTENT= -4;
+    public static final int UNKNOWN_CONTENT= -3;
+    public static final int CHUNKED_CONTENT= -2;
+    public static final int EOF_CONTENT= -1;
+    public static final int NO_CONTENT= 0;
+
+    
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpURI.java b/src/java/org/eclipse/jetty/http/HttpURI.java
new file mode 100644
index 0000000..56cb82c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpURI.java
@@ -0,0 +1,771 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+
+
+/* ------------------------------------------------------------ */
+/** Http URI.
+ * Parse a HTTP URI from a string or byte array.  Given a URI
+ * <code>http://user@host:port/path/info;param?query#fragment</code>
+ * this class will split it into the following undecoded optional elements:<ul>
+ * <li>{@link #getScheme()} - http:</li>
+ * <li>{@link #getAuthority()} - //name@host:port</li>
+ * <li>{@link #getHost()} - host</li>
+ * <li>{@link #getPort()} - port</li>
+ * <li>{@link #getPath()} - /path/info</li>
+ * <li>{@link #getParam()} - param</li>
+ * <li>{@link #getQuery()} - query</li>
+ * <li>{@link #getFragment()} - fragment</li>
+ * </ul>
+ *
+ */
+public class HttpURI
+{
+    private static final byte[] __empty={};
+    private final static int
+    START=0,
+    AUTH_OR_PATH=1,
+    SCHEME_OR_PATH=2,
+    AUTH=4,
+    IPV6=5,
+    PORT=6,
+    PATH=7,
+    PARAM=8,
+    QUERY=9,
+    ASTERISK=10;
+
+    boolean _partial=false;
+    byte[] _raw=__empty;
+    String _rawString;
+    int _scheme;
+    int _authority;
+    int _host;
+    int _port;
+    int _portValue;
+    int _path;
+    int _param;
+    int _query;
+    int _fragment;
+    int _end;
+    boolean _encoded=false;
+
+    final Utf8StringBuilder _utf8b = new Utf8StringBuilder(64);
+
+    public HttpURI()
+    {
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param parsePartialAuth If True, parse auth without prior scheme, else treat all URIs starting with / as paths
+     */
+    public HttpURI(boolean parsePartialAuth)
+    {
+        _partial=parsePartialAuth;
+    }
+
+    public HttpURI(String raw)
+    {
+        _rawString=raw;
+        byte[] b;
+        try
+        {
+            b = raw.getBytes(StringUtil.__UTF8);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+           throw new RuntimeException(e.getMessage());
+        }
+        parse(b,0,b.length);
+    }
+
+    public HttpURI(byte[] raw,int offset, int length)
+    {
+        parse2(raw,offset,length);
+    }
+    
+    public HttpURI(URI uri)
+    {
+        parse(uri.toASCIIString());
+    }
+
+    public void parse(String raw)
+    {
+        byte[] b = raw.getBytes();
+        parse2(b,0,b.length);
+        _rawString=raw;
+    }
+
+    public void parse(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        parse2(raw,offset,length);
+    }
+
+
+    public void parseConnect(byte[] raw,int offset, int length)
+    {
+        _rawString=null;
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=AUTH;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=_end;
+        _portValue=-1;
+        _path=_end;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+
+        loop: while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            switch (state)
+            {
+                case AUTH:
+                {
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            _port = s;
+                            break loop;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+        else
+            throw new IllegalArgumentException("No port");
+        _path=offset;
+    }
+
+
+    private void parse2(byte[] raw,int offset, int length)
+    {
+        _encoded=false;
+        _raw=raw;
+        int i=offset;
+        int e=offset+length;
+        int state=START;
+        int m=offset;
+        _end=offset+length;
+        _scheme=offset;
+        _authority=offset;
+        _host=offset;
+        _port=offset;
+        _portValue=-1;
+        _path=offset;
+        _param=_end;
+        _query=_end;
+        _fragment=_end;
+        while (i<e)
+        {
+            char c=(char)(0xff&_raw[i]);
+            int s=i++;
+
+            state: switch (state)
+            {
+                case START:
+                {
+                    m=s;
+                    switch(c)
+                    {
+                        case '/':
+                            state=AUTH_OR_PATH;
+                            break;
+                        case ';':
+                            _param=s;
+                            state=PARAM;
+                            break;
+                        case '?':
+                            _param=s;
+                            _query=s;
+                            state=QUERY;
+                            break;
+                        case '#':
+                            _param=s;
+                            _query=s;
+                            _fragment=s;
+                            break;
+                        case '*':
+                            _path=s;
+                            state=ASTERISK;
+                            break;
+
+                        default:
+                            state=SCHEME_OR_PATH;
+                    }
+
+                    continue;
+                }
+
+                case AUTH_OR_PATH:
+                {
+                    if ((_partial||_scheme!=_authority) && c=='/')
+                    {
+                        _host=i;
+                        _port=_end;
+                        _path=_end;
+                        state=AUTH;
+                    }
+                    else if (c==';' || c=='?' || c=='#')
+                    {
+                        i--;
+                        state=PATH;
+                    }
+                    else
+                    {
+                        _host=m;
+                        _port=m;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case SCHEME_OR_PATH:
+                {
+                    // short cut for http and https
+                    if (length>6 && c=='t')
+                    {
+                        if (_raw[offset+3]==':')
+                        {
+                            s=offset+3;
+                            i=offset+4;
+                            c=':';
+                        }
+                        else if (_raw[offset+4]==':')
+                        {
+                            s=offset+4;
+                            i=offset+5;
+                            c=':';
+                        }
+                        else if (_raw[offset+5]==':')
+                        {
+                            s=offset+5;
+                            i=offset+6;
+                            c=':';
+                        }
+                    }
+
+                    switch (c)
+                    {
+                        case ':':
+                        {
+                            m = i++;
+                            _authority = m;
+                            _path = m;
+                            c = (char)(0xff & _raw[i]);
+                            if (c == '/')
+                                state = AUTH_OR_PATH;
+                            else
+                            {
+                                _host = m;
+                                _port = m;
+                                state = PATH;
+                            }
+                            break;
+                        }
+
+                        case '/':
+                        {
+                            state = PATH;
+                            break;
+                        }
+
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case AUTH:
+                {
+                    switch (c)
+                    {
+
+                        case '/':
+                        {
+                            m = s;
+                            _path = m;
+                            _port = _path;
+                            state = PATH;
+                            break;
+                        }
+                        case '@':
+                        {
+                            _host = i;
+                            break;
+                        }
+                        case ':':
+                        {
+                            _port = s;
+                            state = PORT;
+                            break;
+                        }
+                        case '[':
+                        {
+                            state = IPV6;
+                            break;
+                        }
+                    }
+                    continue;
+                }
+
+                case IPV6:
+                {
+                    switch (c)
+                    {
+                        case '/':
+                        {
+                            throw new IllegalArgumentException("No closing ']' for " + StringUtil.toString(_raw,offset,length,URIUtil.__CHARSET));
+                        }
+                        case ']':
+                        {
+                            state = AUTH;
+                            break;
+                        }
+                    }
+
+                    continue;
+                }
+
+                case PORT:
+                {
+                    if (c=='/')
+                    {
+                        m=s;
+                        _path=m;
+                        if (_port<=_authority)
+                            _port=_path;
+                        state=PATH;
+                    }
+                    continue;
+                }
+
+                case PATH:
+                {
+                    switch (c)
+                    {
+                        case ';':
+                        {
+                            _param = s;
+                            state = PARAM;
+                            break;
+                        }
+                        case '?':
+                        {
+                            _param = s;
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _param = s;
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                        case '%':
+                        {
+                            _encoded=true;
+                        }
+                    }
+                    continue;
+                }
+
+                case PARAM:
+                {
+                    switch (c)
+                    {
+                        case '?':
+                        {
+                            _query = s;
+                            state = QUERY;
+                            break;
+                        }
+                        case '#':
+                        {
+                            _query = s;
+                            _fragment = s;
+                            break state;
+                        }
+                    }
+                    continue;
+                }
+
+                case QUERY:
+                {
+                    if (c=='#')
+                    {
+                        _fragment=s;
+                        break state;
+                    }
+                    continue;
+                }
+
+                case ASTERISK:
+                {
+                    throw new IllegalArgumentException("only '*'");
+                }
+            }
+        }
+
+        if (_port<_path)
+            _portValue=TypeUtil.parseInt(_raw, _port+1, _path-_port-1,10);
+    }
+
+    private String toUtf8String(int offset,int length)
+    {
+        _utf8b.reset();
+        _utf8b.append(_raw,offset,length);
+        return _utf8b.toString();
+    }
+
+    public String getScheme()
+    {
+        if (_scheme==_authority)
+            return null;
+        int l=_authority-_scheme;
+        if (l==5 &&
+            _raw[_scheme]=='h' &&
+            _raw[_scheme+1]=='t' &&
+            _raw[_scheme+2]=='t' &&
+            _raw[_scheme+3]=='p' )
+            return HttpSchemes.HTTP;
+        if (l==6 &&
+            _raw[_scheme]=='h' &&
+            _raw[_scheme+1]=='t' &&
+            _raw[_scheme+2]=='t' &&
+            _raw[_scheme+3]=='p' &&
+            _raw[_scheme+4]=='s' )
+            return HttpSchemes.HTTPS;
+
+        return toUtf8String(_scheme,_authority-_scheme-1);
+    }
+
+    public String getAuthority()
+    {
+        if (_authority==_path)
+            return null;
+        return toUtf8String(_authority,_path-_authority);
+    }
+
+    public String getHost()
+    {
+        if (_host==_port)
+            return null;
+        return toUtf8String(_host,_port-_host);
+    }
+
+    public int getPort()
+    {
+        return _portValue;
+    }
+
+    public String getPath()
+    {
+        if (_path==_param)
+            return null;
+        return toUtf8String(_path,_param-_path);
+    }
+
+    public String getDecodedPath()
+    {
+        if (_path==_param)
+            return null;
+
+        int length = _param-_path;
+        boolean decoding=false;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (!decoding)
+                {
+                    _utf8b.reset();
+                    _utf8b.append(_raw,_path,i-_path);
+                    decoding=true;
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        _utf8b.getStringBuilder().append(unicode);
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    _utf8b.append(b);
+                    i+=2;
+                }
+                continue;
+            }
+            else if (decoding)
+            {
+                _utf8b.append(b);
+            }
+        }
+
+        if (!decoding)
+            return toUtf8String(_path,length);
+        return _utf8b.toString();
+    }
+    
+    public String getDecodedPath(String encoding)
+    {
+        if (_path==_param)
+            return null;
+
+        int length = _param-_path;
+        byte[] bytes=null;
+        int n=0;
+
+        for (int i=_path;i<_param;i++)
+        {
+            byte b = _raw[i];
+
+            if (b=='%')
+            {
+                if (bytes==null)
+                {
+                    bytes=new byte[length];
+                    System.arraycopy(_raw,_path,bytes,0,n);
+                }
+                
+                if ((i+2)>=_param)
+                    throw new IllegalArgumentException("Bad % encoding: "+this);
+                if (_raw[i+1]=='u')
+                {
+                    if ((i+5)>=_param)
+                        throw new IllegalArgumentException("Bad %u encoding: "+this);
+
+                    try
+                    {
+                        String unicode = new String(Character.toChars(TypeUtil.parseInt(_raw,i+2,4,16)));
+                        byte[] encoded = unicode.getBytes(encoding);
+                        System.arraycopy(encoded,0,bytes,n,encoded.length);
+                        n+=encoded.length;
+                        i+=5;
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    b=(byte)(0xff&TypeUtil.parseInt(_raw,i+1,2,16));
+                    bytes[n++]=b;
+                    i+=2;
+                }
+                continue;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+
+            bytes[n++]=b;
+        }
+
+
+        if (bytes==null)
+            return StringUtil.toString(_raw,_path,_param-_path,encoding);
+
+        return StringUtil.toString(bytes,0,n,encoding);
+    }
+    
+    
+    
+    
+    
+
+
+    public String getPathAndParam()
+    {
+        if (_path==_query)
+            return null;
+        return toUtf8String(_path,_query-_path);
+    }
+
+    public String getCompletePath()
+    {
+        if (_path==_end)
+            return null;
+        return toUtf8String(_path,_end-_path);
+    }
+
+    public String getParam()
+    {
+        if (_param==_query)
+            return null;
+        return toUtf8String(_param+1,_query-_param-1);
+    }
+
+    public String getQuery()
+    {
+        if (_query==_fragment)
+            return null;
+        return toUtf8String(_query+1,_fragment-_query-1);
+    }
+
+    public String getQuery(String encoding)
+    {
+        if (_query==_fragment)
+            return null;
+        return StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding);
+    }
+
+    public boolean hasQuery()
+    {
+        return (_fragment>_query);
+    }
+
+    public String getFragment()
+    {
+        if (_fragment==_end)
+            return null;
+        return toUtf8String(_fragment+1,_end-_fragment-1);
+    }
+
+    public void decodeQueryTo(MultiMap parameters)
+    {
+        if (_query==_fragment)
+            return;
+        _utf8b.reset();
+        UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters,_utf8b);
+    }
+
+    public void decodeQueryTo(MultiMap parameters, String encoding)
+        throws UnsupportedEncodingException
+    {
+        if (_query==_fragment)
+            return;
+
+        if (encoding==null || StringUtil.isUTF8(encoding))
+            UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
+        else
+            UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
+    }
+
+    public void clear()
+    {
+        _scheme=_authority=_host=_port=_path=_param=_query=_fragment=_end=0;
+        _raw=__empty;
+        _rawString="";
+        _encoded=false;
+    }
+
+    @Override
+    public String toString()
+    {
+        if (_rawString==null)
+            _rawString=toUtf8String(_scheme,_end-_scheme);
+        return _rawString;
+    }
+
+    public void writeTo(Utf8StringBuilder buf)
+    {
+        buf.append(_raw,_scheme,_end-_scheme);
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/http/HttpVersions.java b/src/java/org/eclipse/jetty/http/HttpVersions.java
new file mode 100644
index 0000000..147a1a0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/HttpVersions.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * 
+ * 
+ */
+public class HttpVersions
+{
+	public final static String
+		HTTP_0_9 = "",
+		HTTP_1_0 = "HTTP/1.0",
+		HTTP_1_1 = "HTTP/1.1";
+		
+	public final static int
+		HTTP_0_9_ORDINAL=9,
+		HTTP_1_0_ORDINAL=10,
+		HTTP_1_1_ORDINAL=11;
+	
+	public final static BufferCache CACHE = new BufferCache();
+	
+    public final static Buffer 
+        HTTP_0_9_BUFFER=CACHE.add(HTTP_0_9,HTTP_0_9_ORDINAL),
+        HTTP_1_0_BUFFER=CACHE.add(HTTP_1_0,HTTP_1_0_ORDINAL),
+        HTTP_1_1_BUFFER=CACHE.add(HTTP_1_1,HTTP_1_1_ORDINAL);
+}
diff --git a/src/java/org/eclipse/jetty/http/MimeTypes.java b/src/java/org/eclipse/jetty/http/MimeTypes.java
new file mode 100644
index 0000000..7c13920
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/MimeTypes.java
@@ -0,0 +1,376 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * 
+ */
+public class MimeTypes
+{
+    private static final Logger LOG = Log.getLogger(MimeTypes.class);
+
+    public final static String
+      FORM_ENCODED="application/x-www-form-urlencoded",
+      MESSAGE_HTTP="message/http",
+      MULTIPART_BYTERANGES="multipart/byteranges",
+      
+      TEXT_HTML="text/html",
+      TEXT_PLAIN="text/plain",
+      TEXT_XML="text/xml",
+      TEXT_JSON="text/json",
+      
+      TEXT_HTML_8859_1="text/html;charset=ISO-8859-1",
+      TEXT_PLAIN_8859_1="text/plain;charset=ISO-8859-1",
+      TEXT_XML_8859_1="text/xml;charset=ISO-8859-1",
+      
+      TEXT_HTML_UTF_8="text/html;charset=UTF-8",
+      TEXT_PLAIN_UTF_8="text/plain;charset=UTF-8",
+      TEXT_XML_UTF_8="text/xml;charset=UTF-8",
+      TEXT_JSON_UTF_8="text/json;charset=UTF-8";
+
+    private final static String
+      TEXT_HTML__8859_1="text/html; charset=ISO-8859-1",
+      TEXT_PLAIN__8859_1="text/plain; charset=ISO-8859-1",
+      TEXT_XML__8859_1="text/xml; charset=ISO-8859-1",
+      TEXT_HTML__UTF_8="text/html; charset=UTF-8",
+      TEXT_PLAIN__UTF_8="text/plain; charset=UTF-8",
+      TEXT_XML__UTF_8="text/xml; charset=UTF-8",
+      TEXT_JSON__UTF_8="text/json; charset=UTF-8";
+
+    private final static int
+	FORM_ENCODED_ORDINAL=1,
+    	MESSAGE_HTTP_ORDINAL=2,
+    	MULTIPART_BYTERANGES_ORDINAL=3,
+    	
+    	TEXT_HTML_ORDINAL=4,
+	TEXT_PLAIN_ORDINAL=5,
+	TEXT_XML_ORDINAL=6,
+        TEXT_JSON_ORDINAL=7,
+	
+        TEXT_HTML_8859_1_ORDINAL=8,
+        TEXT_PLAIN_8859_1_ORDINAL=9,
+        TEXT_XML_8859_1_ORDINAL=10,
+        
+        TEXT_HTML_UTF_8_ORDINAL=11,
+        TEXT_PLAIN_UTF_8_ORDINAL=12,
+        TEXT_XML_UTF_8_ORDINAL=13,
+        TEXT_JSON_UTF_8_ORDINAL=14;
+    
+    private static int __index=15;
+    
+    public final static BufferCache CACHE = new BufferCache(); 
+
+    public final static CachedBuffer
+    	FORM_ENCODED_BUFFER=CACHE.add(FORM_ENCODED,FORM_ENCODED_ORDINAL),
+    	MESSAGE_HTTP_BUFFER=CACHE.add(MESSAGE_HTTP, MESSAGE_HTTP_ORDINAL),
+    	MULTIPART_BYTERANGES_BUFFER=CACHE.add(MULTIPART_BYTERANGES,MULTIPART_BYTERANGES_ORDINAL),
+        
+        TEXT_HTML_BUFFER=CACHE.add(TEXT_HTML,TEXT_HTML_ORDINAL),
+        TEXT_PLAIN_BUFFER=CACHE.add(TEXT_PLAIN,TEXT_PLAIN_ORDINAL),
+        TEXT_XML_BUFFER=CACHE.add(TEXT_XML,TEXT_XML_ORDINAL),
+        TEXT_JSON_BUFFER=CACHE.add(TEXT_JSON,TEXT_JSON_ORDINAL),
+
+        TEXT_HTML_8859_1_BUFFER=CACHE.add(TEXT_HTML_8859_1,TEXT_HTML_8859_1_ORDINAL),
+        TEXT_PLAIN_8859_1_BUFFER=CACHE.add(TEXT_PLAIN_8859_1,TEXT_PLAIN_8859_1_ORDINAL),
+        TEXT_XML_8859_1_BUFFER=CACHE.add(TEXT_XML_8859_1,TEXT_XML_8859_1_ORDINAL),
+        
+        TEXT_HTML_UTF_8_BUFFER=CACHE.add(TEXT_HTML_UTF_8,TEXT_HTML_UTF_8_ORDINAL),
+        TEXT_PLAIN_UTF_8_BUFFER=CACHE.add(TEXT_PLAIN_UTF_8,TEXT_PLAIN_UTF_8_ORDINAL),
+        TEXT_XML_UTF_8_BUFFER=CACHE.add(TEXT_XML_UTF_8,TEXT_XML_UTF_8_ORDINAL),
+        TEXT_JSON_UTF_8_BUFFER=CACHE.add(TEXT_JSON_UTF_8,TEXT_JSON_UTF_8_ORDINAL),
+
+        TEXT_HTML__8859_1_BUFFER=CACHE.add(TEXT_HTML__8859_1,TEXT_HTML_8859_1_ORDINAL),
+        TEXT_PLAIN__8859_1_BUFFER=CACHE.add(TEXT_PLAIN__8859_1,TEXT_PLAIN_8859_1_ORDINAL),
+        TEXT_XML__8859_1_BUFFER=CACHE.add(TEXT_XML__8859_1,TEXT_XML_8859_1_ORDINAL),
+        
+        TEXT_HTML__UTF_8_BUFFER=CACHE.add(TEXT_HTML__UTF_8,TEXT_HTML_UTF_8_ORDINAL),
+        TEXT_PLAIN__UTF_8_BUFFER=CACHE.add(TEXT_PLAIN__UTF_8,TEXT_PLAIN_UTF_8_ORDINAL),
+        TEXT_XML__UTF_8_BUFFER=CACHE.add(TEXT_XML__UTF_8,TEXT_XML_UTF_8_ORDINAL),
+        TEXT_JSON__UTF_8_BUFFER=CACHE.add(TEXT_JSON__UTF_8,TEXT_JSON_UTF_8_ORDINAL);
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private final static Map __dftMimeMap = new HashMap();
+    private final static Map __encodings = new HashMap();
+    static
+    {
+        try
+        {
+            ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+            Enumeration i = mime.getKeys();
+            while(i.hasMoreElements())
+            {
+                String ext = (String)i.nextElement();
+                String m = mime.getString(ext);
+                __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        try
+        {
+            ResourceBundle encoding = ResourceBundle.getBundle("org/eclipse/jetty/http/encoding");
+            Enumeration i = encoding.getKeys();
+            while(i.hasMoreElements())
+            {
+                Buffer type = normalizeMimeType((String)i.nextElement());
+                __encodings.put(type,encoding.getString(type.toString()));
+            }
+        }
+        catch(MissingResourceException e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        
+        TEXT_HTML_BUFFER.setAssociate("ISO-8859-1",TEXT_HTML_8859_1_BUFFER);
+        TEXT_HTML_BUFFER.setAssociate("ISO_8859_1",TEXT_HTML_8859_1_BUFFER);
+        TEXT_HTML_BUFFER.setAssociate("iso-8859-1",TEXT_HTML_8859_1_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("ISO-8859-1",TEXT_PLAIN_8859_1_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("ISO_8859_1",TEXT_PLAIN_8859_1_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("iso-8859-1",TEXT_PLAIN_8859_1_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("ISO-8859-1",TEXT_XML_8859_1_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("ISO_8859_1",TEXT_XML_8859_1_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("iso-8859-1",TEXT_XML_8859_1_BUFFER);
+
+        TEXT_HTML_BUFFER.setAssociate("UTF-8",TEXT_HTML_UTF_8_BUFFER);
+        TEXT_HTML_BUFFER.setAssociate("UTF8",TEXT_HTML_UTF_8_BUFFER);
+        TEXT_HTML_BUFFER.setAssociate("utf8",TEXT_HTML_UTF_8_BUFFER);
+        TEXT_HTML_BUFFER.setAssociate("utf-8",TEXT_HTML_UTF_8_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("UTF-8",TEXT_PLAIN_UTF_8_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("UTF8",TEXT_PLAIN_UTF_8_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("utf8",TEXT_PLAIN_UTF_8_BUFFER);
+        TEXT_PLAIN_BUFFER.setAssociate("utf-8",TEXT_PLAIN_UTF_8_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("UTF-8",TEXT_XML_UTF_8_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("UTF8",TEXT_XML_UTF_8_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("utf8",TEXT_XML_UTF_8_BUFFER);
+        TEXT_XML_BUFFER.setAssociate("utf-8",TEXT_XML_UTF_8_BUFFER);
+        TEXT_JSON_BUFFER.setAssociate("UTF-8",TEXT_JSON_UTF_8_BUFFER);
+        TEXT_JSON_BUFFER.setAssociate("UTF8",TEXT_JSON_UTF_8_BUFFER);
+        TEXT_JSON_BUFFER.setAssociate("utf8",TEXT_JSON_UTF_8_BUFFER);
+        TEXT_JSON_BUFFER.setAssociate("utf-8",TEXT_JSON_UTF_8_BUFFER);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private Map _mimeMap;
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public MimeTypes()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Map getMimeMap()
+    {
+        return _mimeMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeMap A Map of file extension to mime-type.
+     */
+    public void setMimeMap(Map mimeMap)
+    {
+        if (mimeMap==null)
+        {
+            _mimeMap=null;
+            return;
+        }
+        
+        Map m=new HashMap();
+        Iterator i=mimeMap.entrySet().iterator();
+        while (i.hasNext())
+        {
+            Map.Entry entry = (Map.Entry)i.next();
+            m.put(entry.getKey(),normalizeMimeType(entry.getValue().toString()));
+        }
+        _mimeMap=m;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the MIME type by filename extension.
+     * @param filename A file name
+     * @return MIME type matching the longest dot extension of the
+     * file name.
+     */
+    public Buffer getMimeByExtension(String filename)
+    {
+        Buffer type=null;
+
+        if (filename!=null)
+        {
+            int i=-1;
+            while(type==null)
+            {
+                i=filename.indexOf(".",i+1);
+
+                if (i<0 || i>=filename.length())
+                    break;
+
+                String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
+                if (_mimeMap!=null)
+                    type = (Buffer)_mimeMap.get(ext);
+                if (type==null)
+                    type=(Buffer)__dftMimeMap.get(ext);
+            }
+        }
+
+        if (type==null)
+        {
+            if (_mimeMap!=null)
+                type=(Buffer)_mimeMap.get("*");
+             if (type==null)
+                 type=(Buffer)__dftMimeMap.get("*");
+        }
+
+        return type;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set a mime mapping
+     * @param extension
+     * @param type
+     */
+    public void addMimeMapping(String extension,String type)
+    {
+        if (_mimeMap==null)
+            _mimeMap=new HashMap();
+        
+        _mimeMap.put(StringUtil.asciiToLowerCase(extension),normalizeMimeType(type));
+    }
+
+    /* ------------------------------------------------------------ */
+    private static synchronized Buffer normalizeMimeType(String type)
+    {
+        Buffer b =CACHE.get(type);
+        if (b==null)
+            b=CACHE.add(type,__index++);
+        return b;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String getCharsetFromContentType(Buffer value)
+    {
+        if (value instanceof CachedBuffer)
+        {
+            switch(((CachedBuffer)value).getOrdinal())
+            {
+                case TEXT_HTML_8859_1_ORDINAL:
+                case TEXT_PLAIN_8859_1_ORDINAL:
+                case TEXT_XML_8859_1_ORDINAL:
+                    return StringUtil.__ISO_8859_1;
+
+                case TEXT_HTML_UTF_8_ORDINAL:
+                case TEXT_PLAIN_UTF_8_ORDINAL:
+                case TEXT_XML_UTF_8_ORDINAL:
+                case TEXT_JSON_UTF_8_ORDINAL:
+                    return StringUtil.__UTF8;
+            }
+        }
+        
+        int i=value.getIndex();
+        int end=value.putIndex();
+        int state=0;
+        int start=0;
+        boolean quote=false;
+        for (;i<end;i++)
+        {
+            byte b = value.peek(i);
+            
+            if (quote && state!=10)
+            {
+                if ('"'==b)
+                    quote=false;
+                continue;
+            }
+                
+            switch(state)
+            {
+                case 0:
+                    if ('"'==b)
+                    {
+                        quote=true;
+                        break;
+                    }
+                    if (';'==b)
+                        state=1;
+                    break;
+
+                case 1: if ('c'==b) state=2; else if (' '!=b) state=0; break;
+                case 2: if ('h'==b) state=3; else state=0;break;
+                case 3: if ('a'==b) state=4; else state=0;break;
+                case 4: if ('r'==b) state=5; else state=0;break;
+                case 5: if ('s'==b) state=6; else state=0;break;
+                case 6: if ('e'==b) state=7; else state=0;break;
+                case 7: if ('t'==b) state=8; else state=0;break;
+
+                case 8: if ('='==b) state=9; else if (' '!=b) state=0; break;
+                
+                case 9: 
+                    if (' '==b) 
+                        break;
+                    if ('"'==b) 
+                    {
+                        quote=true;
+                        start=i+1;
+                        state=10;
+                        break;
+                    }
+                    start=i;
+                    state=10;
+                    break;
+                    
+                case 10:
+                    if (!quote && (';'==b || ' '==b )||
+                        (quote && '"'==b ))
+                        return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8);
+            }
+        }    
+        
+        if (state==10)
+            return CACHE.lookup(value.peek(start,i-start)).toString(StringUtil.__UTF8);
+        
+        return (String)__encodings.get(value);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/Parser.java b/src/java/org/eclipse/jetty/http/Parser.java
new file mode 100644
index 0000000..0a045c3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/Parser.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.IOException;
+
+/**
+ * Abstract interface for a connection Parser for use by Jetty.
+ */
+public interface Parser
+{
+    void returnBuffers();
+    void reset();
+
+    boolean isComplete();
+
+    /**
+     * @return True if progress made
+     * @throws IOException
+     */
+    boolean parseAvailable() throws IOException;
+
+    boolean isMoreInBuffer() throws IOException;
+
+    boolean isIdle();
+    
+    boolean isPersistent();
+    
+    void setPersistent(boolean persistent);
+
+}
diff --git a/src/java/org/eclipse/jetty/http/PathMap.java b/src/java/org/eclipse/jetty/http/PathMap.java
new file mode 100644
index 0000000..140bb16
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/PathMap.java
@@ -0,0 +1,589 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.io.Externalizable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** URI path map to Object.
+ * This mapping implements the path specification recommended
+ * in the 2.2 Servlet API.
+ *
+ * Path specifications can be of the following forms:<PRE>
+ * /foo/bar           - an exact path specification.
+ * /foo/*             - a prefix path specification (must end '/*').
+ * *.ext              - a suffix path specification.
+ * /                  - the default path specification.
+ * </PRE>
+ * Matching is performed in the following order <NL>
+ * <LI>Exact match.
+ * <LI>Longest prefix match.
+ * <LI>Longest suffix match.
+ * <LI>default.
+ * </NL>
+ * Multiple path specifications can be mapped by providing a list of
+ * specifications. By default this class uses characters ":," as path
+ * separators, unless configured differently by calling the static
+ * method @see PathMap#setPathSpecSeparators(String)
+ * <P>
+ * Special characters within paths such as '?� and ';' are not treated specially
+ * as it is assumed they would have been either encoded in the original URL or
+ * stripped from the path.
+ * <P>
+ * This class is not synchronized.  If concurrent modifications are
+ * possible then it should be synchronized at a higher level.
+ *
+ *
+ */
+public class PathMap extends HashMap implements Externalizable
+{
+    /* ------------------------------------------------------------ */
+    private static String __pathSpecSeparators = ":,";
+
+    /* ------------------------------------------------------------ */
+    /** Set the path spec separator.
+     * Multiple path specification may be included in a single string
+     * if they are separated by the characters set in this string.
+     * By default this class uses ":," characters as path separators.
+     * @param s separators
+     */
+    public static void setPathSpecSeparators(String s)
+    {
+        __pathSpecSeparators=s;
+    }
+
+    /* --------------------------------------------------------------- */
+    final StringMap _prefixMap=new StringMap();
+    final StringMap _suffixMap=new StringMap();
+    final StringMap _exactMap=new StringMap();
+
+    List _defaultSingletonList=null;
+    Entry _prefixDefault=null;
+    Entry _default=null;
+    final Set _entrySet;
+    boolean _nodefault=false;
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty PathMap.
+     */
+    public PathMap()
+    {
+        super(11);
+        _entrySet=entrySet();
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty PathMap.
+     */
+    public PathMap(boolean nodefault)
+    {
+        super(11);
+        _entrySet=entrySet();
+        _nodefault=nodefault;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty PathMap.
+     */
+    public PathMap(int capacity)
+    {
+        super (capacity);
+        _entrySet=entrySet();
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Construct from dictionary PathMap.
+     */
+    public PathMap(Map m)
+    {
+        putAll(m);
+        _entrySet=entrySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void writeExternal(java.io.ObjectOutput out)
+        throws java.io.IOException
+    {
+        HashMap map = new HashMap(this);
+        out.writeObject(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void readExternal(java.io.ObjectInput in)
+        throws java.io.IOException, ClassNotFoundException
+    {
+        HashMap map = (HashMap)in.readObject();
+        this.putAll(map);
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Add a single path match to the PathMap.
+     * @param pathSpec The path specification, or comma separated list of
+     * path specifications.
+     * @param object The object the path maps to
+     */
+    @Override
+    public Object put(Object pathSpec, Object object)
+    {
+        String str = pathSpec.toString();
+        if ("".equals(str.trim()))
+        {          
+            Entry entry = new Entry("",object);
+            entry.setMapped("");
+            _exactMap.put("", entry);
+            return super.put("", object);
+        }
+        
+        StringTokenizer tok = new StringTokenizer(str,__pathSpecSeparators);
+        Object old =null;
+
+        while (tok.hasMoreTokens())
+        {
+            String spec=tok.nextToken();
+
+            if (!spec.startsWith("/") && !spec.startsWith("*."))
+                throw new IllegalArgumentException("PathSpec "+spec+". must start with '/' or '*.'");
+
+            old = super.put(spec,object);
+
+            // Make entry that was just created.
+            Entry entry = new Entry(spec,object);
+
+            if (entry.getKey().equals(spec))
+            {
+                if (spec.equals("/*"))
+                    _prefixDefault=entry;
+                else if (spec.endsWith("/*"))
+                {
+                    String mapped=spec.substring(0,spec.length()-2);
+                    entry.setMapped(mapped);
+                    _prefixMap.put(mapped,entry);
+                    _exactMap.put(mapped,entry);
+                    _exactMap.put(spec.substring(0,spec.length()-1),entry);
+                }
+                else if (spec.startsWith("*."))
+                    _suffixMap.put(spec.substring(2),entry);
+                else if (spec.equals(URIUtil.SLASH))
+                {
+                    if (_nodefault)
+                        _exactMap.put(spec,entry);
+                    else
+                    {
+                        _default=entry;
+                        _defaultSingletonList=
+                            Collections.singletonList(_default);
+                    }
+                }
+                else
+                {
+                    entry.setMapped(spec);
+                    _exactMap.put(spec,entry);
+                }
+            }
+        }
+
+        return old;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get object matched by the path.
+     * @param path the path.
+     * @return Best matched object or null.
+     */
+    public Object match(String path)
+    {
+        Map.Entry entry = getMatch(path);
+        if (entry!=null)
+            return entry.getValue();
+        return null;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Get the entry mapped by the best specification.
+     * @param path the path.
+     * @return Map.Entry of the best matched  or null.
+     */
+    public Entry getMatch(String path)
+    {
+        Map.Entry entry=null;
+
+        if (path==null)
+            return null;
+
+        int l=path.length();
+        
+        //special case
+        if (l == 1 && path.charAt(0)=='/')
+        {
+            entry = (Map.Entry)_exactMap.get("");
+            if (entry != null)
+                return (Entry)entry;
+        }
+        
+        // try exact match
+        entry=_exactMap.getEntry(path,0,l);
+        if (entry!=null)
+            return (Entry) entry.getValue();
+
+        // prefix search
+        int i=l;
+        while((i=path.lastIndexOf('/',i-1))>=0)
+        {
+            entry=_prefixMap.getEntry(path,0,i);
+            if (entry!=null)
+                return (Entry) entry.getValue();
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            return _prefixDefault;
+
+        // Extension search
+        i=0;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=_suffixMap.getEntry(path,i+1,l-i-1);
+            if (entry!=null)
+                return (Entry) entry.getValue();
+        }
+
+        // Default
+        return _default;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Get all entries matched by the path.
+     * Best match first.
+     * @param path Path to match
+     * @return LazyList of Map.Entry instances key=pathSpec
+     */
+    public Object getLazyMatches(String path)
+    {
+        Map.Entry entry;
+        Object entries=null;
+
+        if (path==null)
+            return LazyList.getList(entries);
+
+        int l=path.length();
+
+        // try exact match
+        entry=_exactMap.getEntry(path,0,l);
+        if (entry!=null)
+            entries=LazyList.add(entries,entry.getValue());
+
+        // prefix search
+        int i=l-1;
+        while((i=path.lastIndexOf('/',i-1))>=0)
+        {
+            entry=_prefixMap.getEntry(path,0,i);
+            if (entry!=null)
+                entries=LazyList.add(entries,entry.getValue());
+        }
+
+        // Prefix Default
+        if (_prefixDefault!=null)
+            entries=LazyList.add(entries,_prefixDefault);
+
+        // Extension search
+        i=0;
+        while ((i=path.indexOf('.',i+1))>0)
+        {
+            entry=_suffixMap.getEntry(path,i+1,l-i-1);
+            if (entry!=null)
+                entries=LazyList.add(entries,entry.getValue());
+        }
+
+        // Default
+        if (_default!=null)
+        {
+            // Optimization for just the default
+            if (entries==null)
+                return _defaultSingletonList;
+
+            entries=LazyList.add(entries,_default);
+        }
+
+        return entries;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Get all entries matched by the path.
+     * Best match first.
+     * @param path Path to match
+     * @return List of Map.Entry instances key=pathSpec
+     */
+    public List getMatches(String path)
+    {
+        return LazyList.getList(getLazyMatches(path));
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Return whether the path matches any entries in the PathMap,
+     * excluding the default entry
+     * @param path Path to match
+     * @return Whether the PathMap contains any entries that match this
+     */
+    public boolean containsMatch(String path)
+    {
+    	Entry match = getMatch(path);
+    	return match!=null && !match.equals(_default);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public Object remove(Object pathSpec)
+    {
+        if (pathSpec!=null)
+        {
+            String spec=(String) pathSpec;
+            if (spec.equals("/*"))
+                _prefixDefault=null;
+            else if (spec.endsWith("/*"))
+            {
+                _prefixMap.remove(spec.substring(0,spec.length()-2));
+                _exactMap.remove(spec.substring(0,spec.length()-1));
+                _exactMap.remove(spec.substring(0,spec.length()-2));
+            }
+            else if (spec.startsWith("*."))
+                _suffixMap.remove(spec.substring(2));
+            else if (spec.equals(URIUtil.SLASH))
+            {
+                _default=null;
+                _defaultSingletonList=null;
+            }
+            else
+                _exactMap.remove(spec);
+        }
+        return super.remove(pathSpec);
+    }
+
+    /* --------------------------------------------------------------- */
+    @Override
+    public void clear()
+    {
+        _exactMap.clear();
+        _prefixMap.clear();
+        _suffixMap.clear();
+        _default=null;
+        _defaultSingletonList=null;
+        super.clear();
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path)
+        throws IllegalArgumentException
+    {
+        return match(pathSpec, path, false);
+    }
+
+    /* --------------------------------------------------------------- */
+    /**
+     * @return true if match.
+     */
+    public static boolean match(String pathSpec, String path, boolean noDefault)
+    throws IllegalArgumentException
+    {
+        if (pathSpec.length()==0)
+            return "/".equals(path);
+            
+        char c = pathSpec.charAt(0);
+        if (c=='/')
+        {
+            if (!noDefault && pathSpec.length()==1 || pathSpec.equals(path))
+                return true;
+
+            if(isPathWildcardMatch(pathSpec, path))
+                return true;
+        }
+        else if (c=='*')
+            return path.regionMatches(path.length()-pathSpec.length()+1,
+                                      pathSpec,1,pathSpec.length()-1);
+        return false;
+    }
+
+    /* --------------------------------------------------------------- */
+    private static boolean isPathWildcardMatch(String pathSpec, String path)
+    {
+        // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+        int cpl=pathSpec.length()-2;
+        if (pathSpec.endsWith("/*") && path.regionMatches(0,pathSpec,0,cpl))
+        {
+            if (path.length()==cpl || '/'==path.charAt(cpl))
+                return true;
+        }
+        return false;
+    }
+
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that matches a path spec.
+     * @return null if no match at all.
+     */
+    public static String pathMatch(String pathSpec, String path)
+    {
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return path;
+
+            if (pathSpec.equals(path))
+                return path;
+
+            if (isPathWildcardMatch(pathSpec, path))
+                return path.substring(0,pathSpec.length()-2);
+        }
+        else if (c=='*')
+        {
+            if (path.regionMatches(path.length()-(pathSpec.length()-1),
+                                   pathSpec,1,pathSpec.length()-1))
+                return path;
+        }
+        return null;
+    }
+
+    /* --------------------------------------------------------------- */
+    /** Return the portion of a path that is after a path spec.
+     * @return The path info string
+     */
+    public static String pathInfo(String pathSpec, String path)
+    {
+        if ("".equals(pathSpec))
+            return path; //servlet 3 spec sec 12.2 will be '/'
+        
+        char c = pathSpec.charAt(0);
+
+        if (c=='/')
+        {
+            if (pathSpec.length()==1)
+                return null;
+
+            boolean wildcard = isPathWildcardMatch(pathSpec, path);
+
+            // handle the case where pathSpec uses a wildcard and path info is "/*"
+            if (pathSpec.equals(path) && !wildcard)
+                return null;
+
+            if (wildcard)
+            {
+                if (path.length()==pathSpec.length()-2)
+                    return null;
+                return path.substring(pathSpec.length()-2);
+            }
+        }
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Relative path.
+     * @param base The base the path is relative to.
+     * @param pathSpec The spec of the path segment to ignore.
+     * @param path the additional path
+     * @return base plus path with pathspec removed
+     */
+    public static String relativePath(String base,
+                                      String pathSpec,
+                                      String path )
+    {
+        String info=pathInfo(pathSpec,path);
+        if (info==null)
+            info=path;
+
+        if( info.startsWith( "./"))
+            info = info.substring( 2);
+        if( base.endsWith( URIUtil.SLASH))
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info.substring(1);
+            else
+                path = base + info;
+        else
+            if( info.startsWith( URIUtil.SLASH))
+                path = base + info;
+            else
+                path = base + URIUtil.SLASH + info;
+        return path;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Entry implements Map.Entry
+    {
+        private final Object key;
+        private final Object value;
+        private String mapped;
+        private transient String string;
+
+        Entry(Object key, Object value)
+        {
+            this.key=key;
+            this.value=value;
+        }
+
+        public Object getKey()
+        {
+            return key;
+        }
+
+        public Object getValue()
+        {
+            return value;
+        }
+
+        public Object setValue(Object o)
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String toString()
+        {
+            if (string==null)
+                string=key+"="+value;
+            return string;
+        }
+
+        public String getMapped()
+        {
+            return mapped;
+        }
+
+        void setMapped(String mapped)
+        {
+            this.mapped = mapped;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java b/src/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java
new file mode 100644
index 0000000..29651c2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/gzip/AbstractCompressedStream.java
@@ -0,0 +1,388 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.zip.DeflaterOutputStream;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/* ------------------------------------------------------------ */
+/**
+ * Skeletal implementation of a CompressedStream. This class adds compression features to a ServletOutputStream and takes care of setting response headers, etc.
+ * Major work and configuration is done here. Subclasses using different kinds of compression only have to implement the abstract methods doCompress() and
+ * setContentEncoding() using the desired compression and setting the appropriate Content-Encoding header string.
+ */
+public abstract class AbstractCompressedStream extends ServletOutputStream 
+{
+    private final String _encoding;
+    protected final String _vary;
+    protected final CompressedResponseWrapper _wrapper;
+    protected final HttpServletResponse _response;
+    protected OutputStream _out;
+    protected ByteArrayOutputStream2 _bOut;
+    protected DeflaterOutputStream _compressedOutputStream;
+    protected boolean _closed;
+    protected boolean _doNotCompress;
+
+    /**
+     * Instantiates a new compressed stream.
+     * 
+     */
+    public AbstractCompressedStream(String encoding,HttpServletRequest request, CompressedResponseWrapper wrapper,String vary)
+            throws IOException
+    {
+        _encoding=encoding;
+        _wrapper = wrapper;
+        _response = (HttpServletResponse)wrapper.getResponse();
+        _vary=vary;
+        
+        if (_wrapper.getMinCompressSize()==0)
+            doCompress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset buffer.
+     */
+    public void resetBuffer()
+    {
+        if (_response.isCommitted() || _compressedOutputStream!=null )
+            throw new IllegalStateException("Committed");
+        _closed = false;
+        _out = null;
+        _bOut = null;
+        _doNotCompress = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setBufferSize(int bufferSize)
+    {
+        if (_bOut!=null && _bOut.getBuf().length<bufferSize)
+        {
+            ByteArrayOutputStream2 b = new ByteArrayOutputStream2(bufferSize);
+            b.write(_bOut.getBuf(),0,_bOut.size());
+            _bOut=b;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setContentLength()
+    {
+        if (_doNotCompress)
+        {
+            long length=_wrapper.getContentLength();
+            if (length>=0)
+            {
+                if (length < Integer.MAX_VALUE)
+                    _response.setContentLength((int)length);
+                else
+                    _response.setHeader("Content-Length",Long.toString(length));
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#flush()
+     */
+    @Override
+    public void flush() throws IOException
+    {
+        if (_out == null || _bOut != null)
+        {
+            long length=_wrapper.getContentLength();
+            if (length > 0 && length < _wrapper.getMinCompressSize())
+                doNotCompress(false);
+            else
+                doCompress();
+        }
+
+        _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#close()
+     */
+    @Override
+    public void close() throws IOException
+    {
+        if (_closed)
+            return;
+
+        if (_wrapper.getRequest().getAttribute("javax.servlet.include.request_uri") != null)
+            flush();
+        else
+        {
+            if (_bOut != null)
+            {
+                long length=_wrapper.getContentLength();
+                if (length < 0)
+                {
+                    length = _bOut.getCount();
+                    _wrapper.setContentLength(length);
+                }
+                if (length < _wrapper.getMinCompressSize())
+                    doNotCompress(false);
+                else
+                    doCompress();
+            }
+            else if (_out == null)
+            {
+                // No output
+                doNotCompress(false);
+            }
+
+            if (_compressedOutputStream != null)
+                _compressedOutputStream.close();
+            else
+                _out.close();
+            _closed = true;
+        }
+    }
+
+    /**
+     * Finish.
+     * 
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    public void finish() throws IOException
+    {
+        if (!_closed)
+        {
+            if (_out == null || _bOut != null)
+            {
+                long length=_wrapper.getContentLength();
+                if (length >= 0 && length < _wrapper.getMinCompressSize())
+                    doNotCompress(false);
+                else
+                    doCompress();
+            }
+
+            if (_compressedOutputStream != null && !_closed)
+            {
+                _closed = true;
+                _compressedOutputStream.close();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(int)
+     */
+    @Override
+    public void write(int b) throws IOException
+    {
+        checkOut(1);
+        _out.write(b);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(byte[])
+     */
+    @Override
+    public void write(byte b[]) throws IOException
+    {
+        checkOut(b.length);
+        _out.write(b);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.io.OutputStream#write(byte[], int, int)
+     */
+    @Override
+    public void write(byte b[], int off, int len) throws IOException
+    {
+        checkOut(len);
+        _out.write(b,off,len);
+    }
+    
+    /**
+     * Do compress.
+     *
+     * @throws IOException Signals that an I/O exception has occurred.
+     */
+    public void doCompress() throws IOException
+    {
+        if (_compressedOutputStream==null) 
+        {
+            if (_response.isCommitted())
+                throw new IllegalStateException();
+            
+            if (_encoding!=null)
+            {
+                setHeader("Content-Encoding", _encoding);            
+                if (_response.containsHeader("Content-Encoding"))
+                {
+                    addHeader("Vary",_vary);
+                    _out=_compressedOutputStream=createStream();
+                    if (_out!=null)
+                    {
+                        if (_bOut!=null)
+                        {
+                            _out.write(_bOut.getBuf(),0,_bOut.getCount());
+                            _bOut=null;
+                        }
+
+                        String etag=_wrapper.getETag();
+                        if (etag!=null)
+                            setHeader("ETag",etag.substring(0,etag.length()-1)+'-'+_encoding+'"');
+                        return;
+                    }
+                }
+            }
+            
+            doNotCompress(true); // Send vary as it could have been compressed if encoding was present
+        }
+    }
+
+    /**
+     * Do not compress.
+     * 
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    public void doNotCompress(boolean sendVary) throws IOException
+    {
+        if (_compressedOutputStream != null)
+            throw new IllegalStateException("Compressed output stream is already assigned.");
+        if (_out == null || _bOut != null)
+        {
+            if (sendVary)
+                addHeader("Vary",_vary);
+            if (_wrapper.getETag()!=null)
+                setHeader("ETag",_wrapper.getETag());
+                
+            _doNotCompress = true;
+
+            _out = _response.getOutputStream();
+            setContentLength();
+
+            if (_bOut != null)
+                _out.write(_bOut.getBuf(),0,_bOut.getCount());
+            _bOut = null;
+        }
+    }
+
+    /**
+     * Check out.
+     * 
+     * @param lengthToWrite
+     *            the length
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    private void checkOut(int lengthToWrite) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+
+        if (_out == null)
+        {            
+            // If this first write is larger than buffer size, then we are committing now
+            if (lengthToWrite>_wrapper.getBufferSize())
+            {
+                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
+                long length=_wrapper.getContentLength();
+                if (length>=0 && length<_wrapper.getMinCompressSize())
+                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
+                else
+                    doCompress();
+            }
+            else
+            {
+                // start aggregating writes into a buffered output stream
+                _out = _bOut = new ByteArrayOutputStream2(_wrapper.getBufferSize());
+            }
+        }
+        // else are we aggregating writes?
+        else if (_bOut !=null)
+        {
+            // We are aggregating into the buffered output stream.  
+
+            // If this write fills the buffer, then we are committing
+            if (lengthToWrite>=(_bOut.getBuf().length - _bOut.getCount()))
+            {
+                // if we know this is all the content and it is less than minimum, then do not compress, otherwise do compress
+                long length=_wrapper.getContentLength();
+                if (length>=0 && length<_wrapper.getMinCompressSize())
+                    doNotCompress(false);  // Not compressing by size, so no vary on request headers
+                else
+                    doCompress();
+            }
+        }
+    }
+
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedStream#getOutputStream()
+     */
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedStream#isClosed()
+     */
+    public boolean isClosed()
+    {
+        return _closed;
+    }
+    
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     */
+    protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
+    {
+        return encoding == null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+
+    protected void addHeader(String name,String value)
+    {
+        _response.addHeader(name, value);
+    }
+
+    protected void setHeader(String name,String value)
+    {
+        _response.setHeader(name, value);
+    }
+    
+    /**
+     * Create the stream fitting to the underlying compression type.
+     * 
+     * @throws IOException
+     *             Signals that an I/O exception has occurred.
+     */
+    protected abstract DeflaterOutputStream createStream() throws IOException;
+
+
+}
diff --git a/src/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java b/src/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java
new file mode 100644
index 0000000..790390c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/gzip/CompressedResponseWrapper.java
@@ -0,0 +1,487 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Set;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/*------------------------------------------------------------ */
+/**
+ */
+public abstract class CompressedResponseWrapper extends HttpServletResponseWrapper
+{
+    
+    public static final int DEFAULT_BUFFER_SIZE = 8192;
+    public static final int DEFAULT_MIN_COMPRESS_SIZE = 256;
+    
+    private Set<String> _mimeTypes;
+    private int _bufferSize=DEFAULT_BUFFER_SIZE;
+    private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE;
+    protected HttpServletRequest _request;
+
+    private PrintWriter _writer;
+    private AbstractCompressedStream _compressedStream;
+    private String _etag;
+    private long _contentLength=-1;
+    private boolean _noCompression;
+
+    /* ------------------------------------------------------------ */
+    public CompressedResponseWrapper(HttpServletRequest request, HttpServletResponse response)
+    {
+        super(response);
+        _request = request;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public long getContentLength()
+    {
+        return _contentLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getMinCompressSize()
+    {
+        return _minCompressSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getETag()
+    {
+        return _etag;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpServletRequest getRequest()
+    {
+        return _request;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMimeTypes(java.util.Set)
+     */
+    public void setMimeTypes(Set<String> mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setBufferSize(int)
+     */
+    @Override
+    public void setBufferSize(int bufferSize)
+    {
+        _bufferSize = bufferSize;
+        if (_compressedStream!=null)
+            _compressedStream.setBufferSize(bufferSize);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setMinCompressSize(int)
+     */
+    public void setMinCompressSize(int minCompressSize)
+    {
+        _minCompressSize = minCompressSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentType(java.lang.String)
+     */
+    @Override
+    public void setContentType(String ct)
+    {
+        super.setContentType(ct);
+    
+        if (!_noCompression)
+        {
+            if (ct!=null)
+            {
+                int colon=ct.indexOf(";");
+                if (colon>0)
+                    ct=ct.substring(0,colon);
+            }
+
+            if ((_compressedStream==null || _compressedStream.getOutputStream()==null) && 
+                    (_mimeTypes==null && ct!=null && ct.contains("gzip") ||
+                    _mimeTypes!=null && (ct==null||!_mimeTypes.contains(StringUtil.asciiToLowerCase(ct)))))
+            {
+                noCompression();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int, java.lang.String)
+     */
+    @Override
+    public void setStatus(int sc, String sm)
+    {
+        super.setStatus(sc,sm);
+        if (sc<200 || sc==204 || sc==205 || sc>=300)
+            noCompression();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setStatus(int)
+     */
+    @Override
+    public void setStatus(int sc)
+    {
+        super.setStatus(sc);
+        if (sc<200 || sc==204 || sc==205 || sc>=300)
+            noCompression();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setContentLength(int)
+     */
+    @Override
+    public void setContentLength(int length)
+    {
+        if (_noCompression)
+            super.setContentLength(length);
+        else
+            setContentLength((long)length);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void setContentLength(long length)
+    {
+        _contentLength=length;
+        if (_compressedStream!=null)
+            _compressedStream.setContentLength();
+        else if (_noCompression && _contentLength>=0)
+        {
+            HttpServletResponse response = (HttpServletResponse)getResponse();
+            if(_contentLength<Integer.MAX_VALUE)
+            {
+                response.setContentLength((int)_contentLength);
+            }
+            else
+            {
+                response.setHeader("Content-Length", Long.toString(_contentLength));
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#addHeader(java.lang.String, java.lang.String)
+     */
+    @Override
+    public void addHeader(String name, String value)
+    {
+        if ("content-length".equalsIgnoreCase(name))
+        {
+            _contentLength=Long.parseLong(value);
+            if (_compressedStream!=null)
+                _compressedStream.setContentLength();
+        }
+        else if ("content-type".equalsIgnoreCase(name))
+        {   
+            setContentType(value);
+        }
+        else if ("content-encoding".equalsIgnoreCase(name))
+        {   
+            super.addHeader(name,value);
+            if (!isCommitted())
+            {
+                noCompression();
+            }
+        }
+        else if ("etag".equalsIgnoreCase(name))
+            _etag=value;
+        else
+            super.addHeader(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#flushBuffer()
+     */
+    @Override
+    public void flushBuffer() throws IOException
+    {
+        if (_writer!=null)
+            _writer.flush();
+        if (_compressedStream!=null)
+            _compressedStream.flush();
+        else
+            getResponse().flushBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#reset()
+     */
+    @Override
+    public void reset()
+    {
+        super.reset();
+        if (_compressedStream!=null)
+            _compressedStream.resetBuffer();
+        _writer=null;
+        _compressedStream=null;
+        _noCompression=false;
+        _contentLength=-1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#resetBuffer()
+     */
+    @Override
+    public void resetBuffer()
+    {
+        super.resetBuffer();
+        if (_compressedStream!=null)
+            _compressedStream.resetBuffer();
+        _writer=null;
+        _compressedStream=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int, java.lang.String)
+     */
+    @Override
+    public void sendError(int sc, String msg) throws IOException
+    {
+        resetBuffer();
+        super.sendError(sc,msg);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendError(int)
+     */
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        resetBuffer();
+        super.sendError(sc);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#sendRedirect(java.lang.String)
+     */
+    @Override
+    public void sendRedirect(String location) throws IOException
+    {
+        resetBuffer();
+        super.sendRedirect(location);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#noCompression()
+     */
+    public void noCompression()
+    {
+        if (!_noCompression)
+            setDeferredHeaders();
+        _noCompression=true;
+        if (_compressedStream!=null)
+        {
+            try
+            {
+                _compressedStream.doNotCompress(false);
+            }
+            catch (IOException e)
+            {
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#finish()
+     */
+    public void finish() throws IOException
+    {
+        if (_writer!=null && !_compressedStream.isClosed())
+            _writer.flush();
+        if (_compressedStream!=null)
+            _compressedStream.finish();
+        else 
+            setDeferredHeaders();
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setDeferredHeaders()
+    {
+        if (!isCommitted())
+        {
+            if (_contentLength>=0)
+            {
+                if (_contentLength < Integer.MAX_VALUE)
+                    super.setContentLength((int)_contentLength);
+                else
+                    super.setHeader("Content-Length",Long.toString(_contentLength));
+            }
+            if(_etag!=null)
+                super.setHeader("ETag",_etag);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setHeader(java.lang.String, java.lang.String)
+     */
+    @Override
+    public void setHeader(String name, String value)
+    {
+        if (_noCompression)
+            super.setHeader(name,value);
+        else if ("content-length".equalsIgnoreCase(name))
+        {
+            setContentLength(Long.parseLong(value));
+        }
+        else if ("content-type".equalsIgnoreCase(name))
+        {   
+            setContentType(value);
+        }
+        else if ("content-encoding".equalsIgnoreCase(name))
+        {   
+            super.setHeader(name,value);
+            if (!isCommitted())
+            {
+                noCompression();
+            }
+        }
+        else if ("etag".equalsIgnoreCase(name))
+            _etag=value;
+        else
+            super.setHeader(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean containsHeader(String name)
+    {
+        if (!_noCompression && "etag".equalsIgnoreCase(name) && _etag!=null)
+            return true;
+        return super.containsHeader(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getOutputStream()
+     */
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        if (_compressedStream==null)
+        {
+            if (getResponse().isCommitted() || _noCompression)
+                return getResponse().getOutputStream();
+            
+            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
+        }
+        else if (_writer!=null)
+            throw new IllegalStateException("getWriter() called");
+        
+        return _compressedStream;   
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#getWriter()
+     */
+    @Override
+    public PrintWriter getWriter() throws IOException
+    {
+        if (_writer==null)
+        { 
+            if (_compressedStream!=null)
+                throw new IllegalStateException("getOutputStream() called");
+            
+            if (getResponse().isCommitted() || _noCompression)
+                return getResponse().getWriter();
+            
+            _compressedStream=newCompressedStream(_request,(HttpServletResponse)getResponse());
+            _writer=newWriter(_compressedStream,getCharacterEncoding());
+        }
+        return _writer;   
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.http.gzip.CompressedResponseWrapper#setIntHeader(java.lang.String, int)
+     */
+    @Override
+    public void setIntHeader(String name, int value)
+    {
+        if ("content-length".equalsIgnoreCase(name))
+        {
+            _contentLength=value;
+            if (_compressedStream!=null)
+                _compressedStream.setContentLength();
+        }
+        else
+            super.setIntHeader(name,value);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     *
+     * @param out the out
+     * @param encoding the encoding
+     * @return the prints the writer
+     * @throws UnsupportedEncodingException the unsupported encoding exception
+     */
+    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+    {
+        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     *@return the underlying CompressedStream implementation 
+     */
+    protected abstract AbstractCompressedStream newCompressedStream(HttpServletRequest _request, HttpServletResponse response) throws IOException;
+
+}
diff --git a/src/java/org/eclipse/jetty/http/ssl/SslContextFactory.java b/src/java/org/eclipse/jetty/http/ssl/SslContextFactory.java
new file mode 100644
index 0000000..b81d070
--- /dev/null
+++ b/src/java/org/eclipse/jetty/http/ssl/SslContextFactory.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http.ssl;
+
+/* ------------------------------------------------------------ */
+/**
+ * @deprecated Use org.eclipse.jetty.util.ssl.SslContextFactory
+ */
+public class SslContextFactory extends org.eclipse.jetty.util.ssl.SslContextFactory
+{
+    public SslContextFactory()
+    {
+        super();
+    }
+
+    public SslContextFactory(boolean trustAll)
+    {
+        super(trustAll);
+    }
+
+    public SslContextFactory(String keyStorePath)
+    {
+        super(keyStorePath);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/AbstractBuffer.java b/src/java/org/eclipse/jetty/io/AbstractBuffer.java
new file mode 100644
index 0000000..19dd727
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/AbstractBuffer.java
@@ -0,0 +1,728 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * 
+ *  
+ */
+public abstract class AbstractBuffer implements Buffer
+{
+    private static final Logger LOG = Log.getLogger(AbstractBuffer.class);
+
+    private final static boolean __boundsChecking = Boolean.getBoolean("org.eclipse.jetty.io.AbstractBuffer.boundsChecking");
+    
+    protected final static String 
+    __IMMUTABLE = "IMMUTABLE", 
+    __READONLY = "READONLY",
+    __READWRITE = "READWRITE", 
+    __VOLATILE = "VOLATILE";
+    
+    protected int _access;
+    protected boolean _volatile;
+
+    protected int _get;
+    protected int _put;
+    protected int _hash;
+    protected int _hashGet;
+    protected int _hashPut;
+    protected int _mark;
+    protected String _string;
+    protected View _view;
+
+    /**
+     * Constructor for BufferView
+     * 
+     * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE
+     */
+    public AbstractBuffer(int access, boolean isVolatile)
+    {
+        if (access == IMMUTABLE && isVolatile)
+                throw new IllegalArgumentException("IMMUTABLE && VOLATILE");
+        setMarkIndex(-1);
+        _access = access;
+        _volatile = isVolatile;
+    }
+
+    /*
+     * @see org.eclipse.io.Buffer#toArray()
+     */
+    public byte[] asArray()
+    {
+        byte[] bytes = new byte[length()];
+        byte[] array = array();
+        if (array != null)
+            System.arraycopy(array, getIndex(), bytes, 0, bytes.length);
+        else
+            peek(getIndex(), bytes, 0, length());
+        return bytes;
+    }
+
+    public ByteArrayBuffer duplicate(int access)
+    {
+        Buffer b=this.buffer();
+        if (this instanceof Buffer.CaseInsensitve || b instanceof Buffer.CaseInsensitve)
+            return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access);
+        else
+            return new ByteArrayBuffer(asArray(), 0, length(), access);
+    }
+    
+    /*
+     * @see org.eclipse.io.Buffer#asNonVolatile()
+     */
+    public Buffer asNonVolatileBuffer()
+    {
+        if (!isVolatile()) return this;
+        return duplicate(_access);
+    }
+
+    public Buffer asImmutableBuffer()
+    {
+        if (isImmutable()) return this;
+        return duplicate(IMMUTABLE);
+    }
+
+    /*
+     * @see org.eclipse.util.Buffer#asReadOnlyBuffer()
+     */
+    public Buffer asReadOnlyBuffer()
+    {
+        if (isReadOnly()) return this;
+        return new View(this, markIndex(), getIndex(), putIndex(), READONLY);
+    }
+
+    public Buffer asMutableBuffer()
+    {
+        if (!isImmutable()) return this;
+        
+        Buffer b=this.buffer();
+        if (b.isReadOnly())
+        {
+            return duplicate(READWRITE);
+        }
+        return new View(b, markIndex(), getIndex(), putIndex(), _access);
+    }
+
+    public Buffer buffer()
+    {
+        return this;
+    }
+
+    public void clear()
+    {
+        setMarkIndex(-1);
+        setGetIndex(0);
+        setPutIndex(0);
+    }
+
+    public void compact()
+    {
+        if (isReadOnly()) throw new IllegalStateException(__READONLY);
+        int s = markIndex() >= 0 ? markIndex() : getIndex();
+        if (s > 0)
+        {
+            byte array[] = array();
+            int length = putIndex() - s;
+            if (length > 0)
+            {
+                if (array != null)
+                    System.arraycopy(array(), s, array(), 0, length);
+                else
+                    poke(0, peek(s, length));
+            }
+            if (markIndex() > 0) setMarkIndex(markIndex() - s);
+            setGetIndex(getIndex() - s);
+            setPutIndex(putIndex() - s);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj==this)
+            return true;
+        
+        // reject non buffers;
+        if (obj == null || !(obj instanceof Buffer)) return false;
+        Buffer b = (Buffer) obj;
+
+        if (this instanceof Buffer.CaseInsensitve ||  b instanceof Buffer.CaseInsensitve)
+            return equalsIgnoreCase(b);
+        
+        // reject different lengths
+        if (b.length() != length()) return false;
+
+        // reject AbstractBuffer with different hash value
+        if (_hash != 0 && obj instanceof AbstractBuffer)
+        {
+            AbstractBuffer ab = (AbstractBuffer) obj;
+            if (ab._hash != 0 && _hash != ab._hash) return false;
+        }
+
+        // Nothing for it but to do the hard grind.
+        int get=getIndex();
+        int bi=b.putIndex();
+        for (int i = putIndex(); i-->get;)
+        {
+            byte b1 = peek(i);
+            byte b2 = b.peek(--bi);
+            if (b1 != b2) return false;
+        }
+        return true;
+    }
+
+    public boolean equalsIgnoreCase(Buffer b)
+    {
+        if (b==this)
+            return true;
+        
+        // reject different lengths
+        if (b.length() != length()) return false;
+
+        // reject AbstractBuffer with different hash value
+        if (_hash != 0 && b instanceof AbstractBuffer)
+        {
+            AbstractBuffer ab = (AbstractBuffer) b;
+            if (ab._hash != 0 && _hash != ab._hash) return false;
+        }
+
+        // Nothing for it but to do the hard grind.
+        int get=getIndex();
+        int bi=b.putIndex();
+        
+        byte[] array = array();
+        byte[] barray= b.array();
+        if (array!=null && barray!=null)
+        {
+            for (int i = putIndex(); i-->get;)
+            {
+                byte b1 = array[i];
+                byte b2 = barray[--bi];
+                if (b1 != b2)
+                {
+                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+                    if (b1 != b2) return false;
+                }
+            }
+        }
+        else
+        {
+            for (int i = putIndex(); i-->get;)
+            {
+                byte b1 = peek(i);
+                byte b2 = b.peek(--bi);
+                if (b1 != b2)
+                {
+                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+                    if (b1 != b2) return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public byte get()
+    {
+        return peek(_get++);
+    }
+
+    public int get(byte[] b, int offset, int length)
+    {
+        int gi = getIndex();
+        int l=length();
+        if (l==0)
+            return -1;
+        
+        if (length>l)
+            length=l;
+        
+        length = peek(gi, b, offset, length);
+        if (length>0)
+            setGetIndex(gi + length);
+        return length;
+    }
+
+    public Buffer get(int length)
+    {
+        int gi = getIndex();
+        Buffer view = peek(gi, length);
+        setGetIndex(gi + length);
+        return view;
+    }
+
+    public final int getIndex()
+    {
+        return _get;
+    }
+
+    public boolean hasContent()
+    {
+        return _put > _get;
+    }
+    
+    @Override
+    public int hashCode()
+    {
+        if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) 
+        {
+            int get=getIndex();
+            byte[] array = array();
+            if (array==null)
+            {
+                for (int i = putIndex(); i-- >get;)
+                {
+                    byte b = peek(i);
+                    if ('a' <= b && b <= 'z') 
+                        b = (byte) (b - 'a' + 'A');
+                    _hash = 31 * _hash + b;
+                }
+            }
+            else
+            {
+                for (int i = putIndex(); i-- >get;)
+                {
+                    byte b = array[i];
+                    if ('a' <= b && b <= 'z') 
+                        b = (byte) (b - 'a' + 'A');
+                    _hash = 31 * _hash + b;
+                }
+            }
+            if (_hash == 0) 
+                _hash = -1;
+            _hashGet=_get;
+            _hashPut=_put;
+            
+        }
+        return _hash;
+    }
+
+    public boolean isImmutable()
+    {
+        return _access <= IMMUTABLE;
+    }
+
+    public boolean isReadOnly()
+    {
+        return _access <= READONLY;
+    }
+
+    public boolean isVolatile()
+    {
+        return _volatile;
+    }
+
+    public int length()
+    {
+        return _put - _get;
+    }
+
+    public void mark()
+    {
+        setMarkIndex(_get - 1);
+    }
+
+    public void mark(int offset)
+    {
+        setMarkIndex(_get + offset);
+    }
+
+    public int markIndex()
+    {
+        return _mark;
+    }
+
+    public byte peek()
+    {
+        return peek(_get);
+    }
+
+    public Buffer peek(int index, int length)
+    {
+        if (_view == null)
+        {
+            _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE);
+        }
+        else
+        {
+            _view.update(this.buffer());
+            _view.setMarkIndex(-1);
+            _view.setGetIndex(0);
+            _view.setPutIndex(index + length);
+            _view.setGetIndex(index);
+            
+        }
+        return _view;
+    }
+
+    public int poke(int index, Buffer src)
+    {
+        _hash=0;
+        /* 
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        if (index < 0) 
+            throw new IllegalArgumentException("index<0: " + index + "<0");
+        */
+        
+        int length=src.length();
+        if (index + length > capacity())
+        {
+            length=capacity()-index;
+            /*
+            if (length<0)
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+            */
+        }
+        
+        byte[] src_array = src.array();
+        byte[] dst_array = array();
+        if (src_array != null && dst_array != null)
+            System.arraycopy(src_array, src.getIndex(), dst_array, index, length);
+        else if (src_array != null)
+        {
+            int s=src.getIndex();
+            for (int i=0;i<length;i++)
+                poke(index++,src_array[s++]);
+        }
+        else if (dst_array != null)
+        {
+            int s=src.getIndex();
+            for (int i=0;i<length;i++)
+                dst_array[index++]=src.peek(s++);
+        }
+        else
+        {
+            int s=src.getIndex();
+            for (int i=0;i<length;i++)
+                poke(index++,src.peek(s++));
+        }
+        
+        return length;
+    }
+    
+
+    public int poke(int index, byte[] b, int offset, int length)
+    {
+        _hash=0;
+        /*
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        if (index < 0) 
+            throw new IllegalArgumentException("index<0: " + index + "<0");
+        */
+        if (index + length > capacity())
+        {
+            length=capacity()-index;
+            /* if (length<0)
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+            */
+        }
+        
+        byte[] dst_array = array();
+        if (dst_array != null)
+            System.arraycopy(b, offset, dst_array, index, length);
+        else
+        {
+            int s=offset;
+            for (int i=0;i<length;i++)
+                poke(index++,b[s++]);
+        }
+        return length;
+    }
+
+    public int put(Buffer src)
+    {
+        int pi = putIndex();
+        int l=poke(pi, src);
+        setPutIndex(pi + l);
+        return l;
+    }
+
+    public void put(byte b)
+    {
+        int pi = putIndex();
+        poke(pi, b);
+        setPutIndex(pi + 1);
+    }
+
+    public int put(byte[] b, int offset, int length)
+    {
+        int pi = putIndex();
+        int l = poke(pi, b, offset, length);
+        setPutIndex(pi + l);
+        return l;
+    }
+    
+    public int put(byte[] b)
+    {
+        int pi = putIndex();
+        int l = poke(pi, b, 0, b.length);
+        setPutIndex(pi + l);
+        return l;
+    }
+
+    public final int putIndex()
+    {
+        return _put;
+    }
+
+    public void reset()
+    {
+        if (markIndex() >= 0) setGetIndex(markIndex());
+    }
+
+    public void rewind()
+    {
+        setGetIndex(0);
+        setMarkIndex(-1);
+    }
+
+    public void setGetIndex(int getIndex)
+    {
+        /* bounds checking
+        if (isImmutable()) 
+            throw new IllegalStateException(__IMMUTABLE);
+        if (getIndex < 0)
+            throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0");
+        if (getIndex > putIndex())
+            throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex());
+         */
+        _get = getIndex;
+        _hash=0;
+    }
+
+    public void setMarkIndex(int index)
+    {
+        /*
+        if (index>=0 && isImmutable()) 
+            throw new IllegalStateException(__IMMUTABLE);
+        */
+        _mark = index;
+    }
+
+    public void setPutIndex(int putIndex)
+    {
+        /* bounds checking
+        if (isImmutable()) 
+            throw new IllegalStateException(__IMMUTABLE);
+        if (putIndex > capacity())
+                throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity());
+        if (getIndex() > putIndex)
+                throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex);
+         */
+        _put = putIndex;
+        _hash=0;
+    }
+
+    public int skip(int n)
+    {
+        if (length() < n) n = length();
+        setGetIndex(getIndex() + n);
+        return n;
+    }
+
+    public Buffer slice()
+    {
+        return peek(getIndex(), length());
+    }
+
+    public Buffer sliceFromMark()
+    {
+        return sliceFromMark(getIndex() - markIndex() - 1);
+    }
+
+    public Buffer sliceFromMark(int length)
+    {
+        if (markIndex() < 0) return null;
+        Buffer view = peek(markIndex(), length);
+        setMarkIndex(-1);
+        return view;
+    }
+
+    public int space()
+    {
+        return capacity() - _put;
+    }
+
+    public String toDetailString()
+    {
+        StringBuilder buf = new StringBuilder();
+        buf.append("[");
+        buf.append(super.hashCode());
+        buf.append(",");
+        buf.append(this.buffer().hashCode());
+        buf.append(",m=");
+        buf.append(markIndex());
+        buf.append(",g=");
+        buf.append(getIndex());
+        buf.append(",p=");
+        buf.append(putIndex());
+        buf.append(",c=");
+        buf.append(capacity());
+        buf.append("]={");
+        if (markIndex() >= 0)
+        {
+            for (int i = markIndex(); i < getIndex(); i++)
+            {
+                byte b =  peek(i);
+                TypeUtil.toHex(b,buf);
+            }
+            buf.append("}{");
+        }
+        int count = 0;
+        for (int i = getIndex(); i < putIndex(); i++)
+        {
+            byte b =  peek(i);
+            TypeUtil.toHex(b,buf);
+            if (count++ == 50)
+            {
+                if (putIndex() - i > 20)
+                {
+                    buf.append(" ... ");
+                    i = putIndex() - 20;
+                }
+            }
+        }
+        buf.append('}');
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        if (isImmutable())
+        {
+            if (_string == null) 
+                _string = new String(asArray(), 0, length());
+            return _string;
+        }
+        return new String(asArray(), 0, length());
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString(String charset)
+    {
+        try
+        {
+            byte[] bytes=array();
+            if (bytes!=null)
+                return new String(bytes,getIndex(),length(),charset);
+            return new String(asArray(), 0, length(),charset);
+            
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return new String(asArray(), 0, length());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString(Charset charset)
+    {
+        try
+        {
+            byte[] bytes=array();
+            if (bytes!=null)
+                return new String(bytes,getIndex(),length(),charset);
+            return new String(asArray(), 0, length(),charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return new String(asArray(), 0, length());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toDebugString()
+    {
+        return getClass()+"@"+super.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void writeTo(OutputStream out)
+    	throws IOException
+    {
+        byte[] array = array();
+        
+        if (array!=null)
+        {
+            out.write(array,getIndex(),length());
+        }
+        else
+        {
+            int len = this.length();
+            byte[] buf=new byte[len>1024?1024:len];
+            int offset=_get;
+            while (len>0)
+            {
+                int l=peek(offset,buf,0,len>buf.length?buf.length:len);
+                out.write(buf,0,l);
+                offset+=l;
+                len-=l;
+            }
+        } 
+        clear();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int readFrom(InputStream in,int max) throws IOException
+    {
+        byte[] array = array();
+        int s=space();
+        if (s>max)
+            s=max;
+
+        if (array!=null)
+        {
+            int l=in.read(array,_put,s);
+            if (l>0)
+                _put+=l;
+            return l;
+        }
+        else
+        {
+            byte[] buf=new byte[s>1024?1024:s];
+            int total=0;
+            while (s>0)
+            {
+                int l=in.read(buf,0,buf.length);
+                if (l<0)
+                    return total>0?total:-1;
+                int p=put(buf,0,l);
+                assert l==p;
+                s-=l;
+            }
+            return total; 
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/AbstractBuffers.java b/src/java/org/eclipse/jetty/io/AbstractBuffers.java
new file mode 100644
index 0000000..aa7aa8c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/AbstractBuffers.java
@@ -0,0 +1,168 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+
+public abstract class AbstractBuffers implements Buffers
+{
+    protected final Buffers.Type _headerType;
+    protected final int _headerSize;
+    protected final Buffers.Type _bufferType;
+    protected final int _bufferSize;
+    protected final Buffers.Type _otherType;
+
+    /* ------------------------------------------------------------ */
+    public AbstractBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
+    {
+        _headerType=headerType;
+        _headerSize=headerSize;
+        _bufferType=bufferType;
+        _bufferSize=bufferSize;
+        _otherType=otherType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the buffer size in bytes.
+     */
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the header size in bytes.
+     */
+    public int getHeaderSize()
+    {
+        return _headerSize;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new header Buffer
+     * @return new Buffer
+     */
+    final protected Buffer newHeader()
+    {
+        switch(_headerType)
+        {
+            case BYTE_ARRAY:
+                return new ByteArrayBuffer(_headerSize);
+            case DIRECT:
+                return new DirectNIOBuffer(_headerSize);
+            case INDIRECT:
+                return new IndirectNIOBuffer(_headerSize);
+        }
+        throw new IllegalStateException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new content Buffer
+     * @return new Buffer
+     */
+    final protected Buffer newBuffer()
+    {
+       switch(_bufferType)
+       {
+           case BYTE_ARRAY:
+               return new ByteArrayBuffer(_bufferSize);
+           case DIRECT:
+               return new DirectNIOBuffer(_bufferSize);
+           case INDIRECT:
+               return new IndirectNIOBuffer(_bufferSize);
+       }
+       throw new IllegalStateException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new content Buffer
+     * @param size
+     * @return new Buffer
+     */
+    final protected Buffer newBuffer(int size)
+    {
+       switch(_otherType)
+       {
+           case BYTE_ARRAY:
+               return new ByteArrayBuffer(size);
+           case DIRECT:
+               return new DirectNIOBuffer(size);
+           case INDIRECT:
+               return new IndirectNIOBuffer(size);
+       }
+       throw new IllegalStateException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffer
+     * @return True if the buffer is the correct type to be a Header buffer
+     */
+    public final boolean isHeader(Buffer buffer)
+    {
+        if (buffer.capacity()==_headerSize)
+        {
+            switch(_headerType)
+            {
+                case BYTE_ARRAY:
+                    return buffer instanceof ByteArrayBuffer && !(buffer instanceof  IndirectNIOBuffer);
+                case DIRECT:
+                    return buffer instanceof  DirectNIOBuffer;
+                case INDIRECT:
+                    return buffer instanceof  IndirectNIOBuffer;
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffer
+     * @return True if the buffer is the correct type to be a Header buffer
+     */
+    public final boolean isBuffer(Buffer buffer)
+    {
+        if (buffer.capacity()==_bufferSize)
+        {
+            switch(_bufferType)
+            {
+                case BYTE_ARRAY:
+                    return buffer instanceof ByteArrayBuffer && !(buffer instanceof  IndirectNIOBuffer);
+                case DIRECT:
+                    return buffer instanceof  DirectNIOBuffer;
+                case INDIRECT:
+                    return buffer instanceof  IndirectNIOBuffer;
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return String.format("%s [%d,%d]", getClass().getSimpleName(), _headerSize, _bufferSize);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/AbstractConnection.java b/src/java/org/eclipse/jetty/io/AbstractConnection.java
new file mode 100644
index 0000000..7aa846c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -0,0 +1,85 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+public abstract class AbstractConnection implements Connection
+{
+    private static final Logger LOG = Log.getLogger(AbstractConnection.class);
+
+    private final long _timeStamp;
+    protected final EndPoint _endp;
+
+    public AbstractConnection(EndPoint endp)
+    {
+        _endp=(EndPoint)endp;
+        _timeStamp = System.currentTimeMillis();
+    }
+
+    public AbstractConnection(EndPoint endp,long timestamp)
+    {
+        _endp=(EndPoint)endp;
+        _timeStamp = timestamp;
+    }
+
+    public long getTimeStamp()
+    {
+        return _timeStamp;
+    }
+
+    public EndPoint getEndPoint()
+    {
+        return _endp;
+    }
+
+    public void onIdleExpired(long idleForMs)
+    {
+        try
+        {
+            LOG.debug("onIdleExpired {}ms {} {}",idleForMs,this,_endp);
+            if (_endp.isInputShutdown() || _endp.isOutputShutdown())
+                _endp.close();
+            else
+                _endp.shutdownOutput();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+
+            try
+            {
+                _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+        }
+    }
+
+    public String toString()
+    {
+        return String.format("%s@%x", getClass().getSimpleName(), hashCode());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/AsyncEndPoint.java b/src/java/org/eclipse/jetty/io/AsyncEndPoint.java
new file mode 100644
index 0000000..42ec421
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/AsyncEndPoint.java
@@ -0,0 +1,83 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import org.eclipse.jetty.util.thread.Timeout;
+
+public interface AsyncEndPoint extends ConnectedEndPoint
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Dispatch the endpoint if it is not already dispatched
+     * 
+     */
+    public void dispatch();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Dispatch the endpoint. If it is already dispatched, schedule a redispatch
+     * 
+     */
+    public void asyncDispatch();
+    
+    /* ------------------------------------------------------------ */
+    /** Schedule a write dispatch.
+     * Set the endpoint to not be writable and schedule a dispatch when
+     * it becomes writable.
+     */
+    public void scheduleWrite();  
+
+    /* ------------------------------------------------------------ */
+    /** Callback when idle.
+     * <p>An endpoint is idle if there has been no IO activity for 
+     * {@link #getMaxIdleTime()} and {@link #isCheckForIdle()} is true.
+     * @param idleForMs TODO
+     */
+    public void onIdleExpired(long idleForMs);
+
+    /* ------------------------------------------------------------ */
+    /** Set if the endpoint should be checked for idleness
+     */
+    public void setCheckForIdle(boolean check);
+
+    /* ------------------------------------------------------------ */
+    /** Get if the endpoint should be checked for idleness
+     */
+    public boolean isCheckForIdle();
+
+    
+    /* ------------------------------------------------------------ */
+    public boolean isWritable();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if IO has been successfully performed since the last call to {@link #hasProgressed()}
+     */
+    public boolean hasProgressed();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void scheduleTimeout(Timeout.Task task, long timeoutMs);
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void cancelTimeout(Timeout.Task task);
+}
diff --git a/src/java/org/eclipse/jetty/io/Buffer.java b/src/java/org/eclipse/jetty/io/Buffer.java
new file mode 100644
index 0000000..fb79bf2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/Buffer.java
@@ -0,0 +1,380 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+
+/**
+ * Byte Buffer interface.
+ * 
+ * This is a byte buffer that is designed to work like a FIFO for bytes. Puts and Gets operate on different
+ * pointers into the buffer and the valid _content of the buffer is always between the getIndex and the putIndex.
+ * 
+ * This buffer interface is designed to be similar, but not dependent on the java.nio buffers, which may
+ * be used to back an implementation of this Buffer. The main difference is that NIO buffer after a put have 
+ * their valid _content before the position and a flip is required to access that data.
+ *
+ * For this buffer it is always true that:
+ *  markValue <= getIndex <= putIndex <= capacity
+ *  
+ *
+ * @version 1.0
+ */
+public interface Buffer extends Cloneable
+{
+    public final static int 
+      IMMUTABLE=0,  // neither indexes or contexts can be changed
+      READONLY=1,   // indexes may be changed, but not content
+      READWRITE=2;  // anything can be changed
+    public final boolean VOLATILE=true;     // The buffer may change outside of current scope.
+    public final boolean NON_VOLATILE=false;
+
+    /**
+     *  Get the underlying array, if one exists.
+     * @return a <code>byte[]</code> backing this buffer or null if none exists.
+     */
+    byte[] array();
+    
+    /**
+     * 
+     * @return a <code>byte[]</code> value of the bytes from the getIndex to the putIndex.
+     */
+    byte[] asArray();
+    
+    /** 
+     * Get the underlying buffer. If this buffer wraps a backing buffer.
+     * @return The root backing buffer or this if there is no backing buffer;
+     */
+    Buffer buffer();
+    
+    /**
+     * 
+     * @return a non volatile version of this <code>Buffer</code> value
+     */
+    Buffer asNonVolatileBuffer();
+
+    /**
+     *
+     * @return a readonly version of this <code>Buffer</code>.
+     */
+    Buffer asReadOnlyBuffer();
+
+    /**
+     *
+     * @return an immutable version of this <code>Buffer</code>.
+     */
+    Buffer asImmutableBuffer();
+
+    /**
+     *
+     * @return an immutable version of this <code>Buffer</code>.
+     */
+    Buffer asMutableBuffer();
+    
+    /**
+     * 
+     * The capacity of the buffer. This is the maximum putIndex that may be set.
+     * @return an <code>int</code> value
+     */
+    int capacity();
+    
+    /**
+     * the space remaining in the buffer.
+     * @return capacity - putIndex
+     */
+    int space();
+    
+    /**
+     * Clear the buffer. getIndex=0, putIndex=0.
+     */
+    void clear();
+
+    /**
+     * Compact the buffer by discarding bytes before the postion (or mark if set).
+     * Bytes from the getIndex (or mark) to the putIndex are moved to the beginning of 
+     * the buffer and the values adjusted accordingly.
+     */
+    void compact();
+    
+    /**
+     * Get the byte at the current getIndex and increment it.
+     * @return The <code>byte</code> value from the current getIndex.
+     */
+    byte get();
+    
+    /**
+     * Get bytes from the current postion and put them into the passed byte array.
+     * The getIndex is incremented by the number of bytes copied into the array.
+     * @param b The byte array to fill.
+     * @param offset Offset in the array.
+     * @param length The max number of bytes to read.
+     * @return The number of bytes actually read.
+     */
+    int get(byte[] b, int offset, int length);
+
+    /**
+     * 
+     * @param length an <code>int</code> value
+     * @return a <code>Buffer</code> value
+     */
+    Buffer get(int length);
+
+    /**
+     * The index within the buffer that will next be read or written.
+     * @return an <code>int</code> value >=0 <= putIndex()
+     */
+    int getIndex();
+    
+    /**
+     * @return true of putIndex > getIndex
+     */
+    boolean hasContent();
+    
+    /**
+     * 
+     * @return a <code>boolean</code> value true if case sensitive comparison on this buffer
+     */
+    boolean equalsIgnoreCase(Buffer buffer);
+
+
+    /**
+     * 
+     * @return a <code>boolean</code> value true if the buffer is immutable and that neither
+     * the buffer contents nor the indexes may be changed.
+     */
+    boolean isImmutable();
+    
+    /**
+     * 
+     * @return a <code>boolean</code> value true if the buffer is readonly. The buffer indexes may
+     * be modified, but the buffer contents may not. For example a View onto an immutable Buffer will be
+     * read only.
+     */
+    boolean isReadOnly();
+    
+    /**
+     * 
+     * @return a <code>boolean</code> value true if the buffer contents may change 
+     * via alternate paths than this buffer.  If the contents of this buffer are to be used outside of the
+     * current context, then a copy must be made.
+     */
+    boolean isVolatile();
+
+    /**
+     * The number of bytes from the getIndex to the putIndex
+     * @return an <code>int</code> == putIndex()-getIndex()
+     */
+    int length();
+    
+    /**
+     * Set the mark to the current getIndex.
+     */
+    void mark();
+    
+    /**
+     * Set the mark relative to the current getIndex
+     * @param offset an <code>int</code> value to add to the current getIndex to obtain the mark value.
+     */
+    void mark(int offset);
+
+    /**
+     * The current index of the mark.
+     * @return an <code>int</code> index in the buffer or -1 if the mark is not set.
+     */
+    int markIndex();
+
+    /**
+     * Get the byte at the current getIndex without incrementing the getIndex.
+     * @return The <code>byte</code> value from the current getIndex.
+     */
+    byte peek();
+  
+    /**
+     * Get the byte at a specific index in the buffer.
+     * @param index an <code>int</code> value
+     * @return a <code>byte</code> value
+     */
+    byte peek(int index);
+
+    /**
+     * 
+     * @param index an <code>int</code> value
+     * @param length an <code>int</code> value
+     * @return The <code>Buffer</code> value from the requested getIndex.
+     */
+    Buffer peek(int index, int length);
+
+    /**
+     * 
+     * @param index an <code>int</code> value
+     * @param b The byte array to peek into
+     * @param offset The offset into the array to start peeking
+     * @param length an <code>int</code> value
+     * @return The number of bytes actually peeked
+     */
+    int peek(int index, byte[] b, int offset, int length);
+    
+    /**
+     * Put the contents of the buffer at the specific index.
+     * @param index an <code>int</code> value
+     * @param src a <code>Buffer</code>. If the source buffer is not modified
+    
+     * @return The number of bytes actually poked
+     */
+    int poke(int index, Buffer src);
+    
+    /**
+     * Put a specific byte to a specific getIndex.
+     * @param index an <code>int</code> value
+     * @param b a <code>byte</code> value
+     */
+    void poke(int index, byte b);
+    
+    /**
+     * Put a specific byte to a specific getIndex.
+     * @param index an <code>int</code> value
+     * @param b a <code>byte array</code> value
+     * @return The number of bytes actually poked
+     */
+    int poke(int index, byte b[], int offset, int length);
+    
+    /**
+     * Write the bytes from the source buffer to the current getIndex.
+     * @param src The source <code>Buffer</code> it is not modified.
+     * @return The number of bytes actually poked
+     */
+    int put(Buffer src);
+
+    /**
+     * Put a byte to the current getIndex and increment the getIndex.
+     * @param b a <code>byte</code> value
+     */
+    void put(byte b);
+    
+    /**
+     * Put a byte to the current getIndex and increment the getIndex.
+     * @param b a <code>byte</code> value
+     * @return The number of bytes actually poked
+     */
+    int put(byte[] b,int offset, int length);
+
+    /**
+     * Put a byte to the current getIndex and increment the getIndex.
+     * @param b a <code>byte</code> value
+     * @return The number of bytes actually poked
+     */
+    int put(byte[] b);
+
+    /**
+     * The index of the first element that should not be read.
+     * @return an <code>int</code> value >= getIndex() 
+     */
+    int putIndex();
+    
+    /**
+     * Reset the current getIndex to the mark 
+     */
+    void reset();
+    
+    /**
+     * Set the buffers start getIndex.
+     * @param newStart an <code>int</code> value
+     */
+    void setGetIndex(int newStart);
+    
+    /**
+     * Set a specific value for the mark.
+     * @param newMark an <code>int</code> value
+     */
+    void setMarkIndex(int newMark);
+    
+    /**
+     * 
+     * @param newLimit an <code>int</code> value
+     */
+    void setPutIndex(int newLimit);
+    
+    /**
+     * Skip _content. The getIndex is updated by min(remaining(), n)
+     * @param n The number of bytes to skip
+     * @return the number of bytes skipped.
+     */
+    int skip(int n);
+
+    /**
+     * 
+     * @return a volitile <code>Buffer</code> from the postion to the putIndex.
+     */
+    Buffer slice();
+    
+    /**
+     * 
+     *
+     * @return a volitile <code>Buffer</code> value from the mark to the putIndex
+     */
+    Buffer sliceFromMark();
+    
+    /**
+     * 
+     *
+     * @param length an <code>int</code> value
+     * @return a valitile <code>Buffer</code> value from the mark of the length requested.
+     */
+    Buffer sliceFromMark(int length);
+    
+    /**
+     * 
+     * @return a <code>String</code> value describing the state and contents of the buffer.
+     */
+    String toDetailString();
+
+    /* ------------------------------------------------------------ */
+    /** Write the buffer's contents to the output stream
+     * @param out
+     */
+    void writeTo(OutputStream out) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /** Read the buffer's contents from the input stream
+     * @param in input stream
+     * @param max maximum number of bytes that may be read
+     * @return actual number of bytes read or -1 for EOF
+     */
+    int readFrom(InputStream in, int max) throws IOException;
+    
+
+    /* ------------------------------------------------------------ */
+    String toString(String charset);
+    
+    /* ------------------------------------------------------------ */
+    String toString(Charset charset);
+
+    /*
+     * Buffers implementing this interface should be compared with case insensitive equals
+     *
+     */
+    public interface CaseInsensitve
+    {}
+
+    
+}
diff --git a/src/java/org/eclipse/jetty/io/BufferCache.java b/src/java/org/eclipse/jetty/io/BufferCache.java
new file mode 100644
index 0000000..f61857a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/BufferCache.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+import org.eclipse.jetty.util.StringMap;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * Stores a collection of {@link Buffer} objects.
+ * Buffers are stored in an ordered collection and can retreived by index or value
+ * 
+ */
+public class BufferCache
+{
+    private final HashMap _bufferMap=new HashMap();
+    private final StringMap _stringMap=new StringMap(StringMap.CASE_INSENSTIVE);
+    private final ArrayList _index= new ArrayList();
+
+    /* ------------------------------------------------------------------------------- */
+    /** Add a buffer to the cache at the specified index.
+     * @param value The content of the buffer.
+     */
+    public CachedBuffer add(String value, int ordinal)
+    {
+        CachedBuffer buffer= new CachedBuffer(value, ordinal);
+        _bufferMap.put(buffer, buffer);
+        _stringMap.put(value, buffer);
+        while ((ordinal - _index.size()) >= 0)
+            _index.add(null);
+        if (_index.get(ordinal)==null)
+            _index.add(ordinal, buffer);
+        return buffer;
+    }
+
+    public CachedBuffer get(int ordinal)
+    {
+        if (ordinal < 0 || ordinal >= _index.size())
+            return null;
+        return (CachedBuffer)_index.get(ordinal);
+    }
+
+    public CachedBuffer get(Buffer buffer)
+    {
+        return (CachedBuffer)_bufferMap.get(buffer);
+    }
+
+    public CachedBuffer get(String value)
+    {
+        return (CachedBuffer)_stringMap.get(value);
+    }
+
+    public Buffer lookup(Buffer buffer)
+    {
+        if (buffer instanceof CachedBuffer)
+            return buffer;
+        
+        Buffer b= get(buffer);
+        if (b == null)
+        {
+            if (buffer instanceof Buffer.CaseInsensitve)
+                return buffer;
+            return new ByteArrayBuffer.CaseInsensitive(buffer.asArray(),0,buffer.length(),Buffer.IMMUTABLE);
+        }
+
+        return b;
+    }
+    
+    public CachedBuffer getBest(byte[] value, int offset, int maxLength)
+    {
+        Entry entry = _stringMap.getBestEntry(value, offset, maxLength);
+        if (entry!=null)
+            return (CachedBuffer)entry.getValue();
+        return null;
+    }
+
+    public Buffer lookup(String value)
+    {
+        Buffer b= get(value);
+        if (b == null)
+        {
+            return new CachedBuffer(value,-1);
+        }
+        return b;
+    }
+
+    public String toString(Buffer buffer)
+    {
+        return lookup(buffer).toString();
+    }
+
+    public int getOrdinal(String value)
+    {
+        CachedBuffer buffer = (CachedBuffer)_stringMap.get(value);
+        return buffer==null?-1:buffer.getOrdinal();
+    }
+    
+    public int getOrdinal(Buffer buffer)
+    {
+        if (buffer instanceof CachedBuffer)
+            return ((CachedBuffer)buffer).getOrdinal();
+        buffer=lookup(buffer);
+        if (buffer!=null && buffer instanceof CachedBuffer)
+            return ((CachedBuffer)buffer).getOrdinal();
+        return -1;
+    }
+    
+    public static class CachedBuffer extends ByteArrayBuffer.CaseInsensitive
+    {
+        private final int _ordinal;
+        private HashMap _associateMap=null;
+        
+        public CachedBuffer(String value, int ordinal)
+        {
+            super(value);
+            _ordinal= ordinal;
+        }
+
+        public int getOrdinal()
+        {
+            return _ordinal;
+        }
+
+        public CachedBuffer getAssociate(Object key)
+        {
+            if (_associateMap==null)
+                return null;
+            return (CachedBuffer)_associateMap.get(key);
+        }
+
+        // TODO Replace Associate with a mime encoding specific solution
+        public void setAssociate(Object key, CachedBuffer associate)
+        {
+            if (_associateMap==null)
+                _associateMap=new HashMap();
+            _associateMap.put(key,associate);
+        }
+    }
+    
+    
+    @Override
+    public String toString()
+    {
+        return "CACHE["+
+        	"bufferMap="+_bufferMap+
+        	",stringMap="+_stringMap+
+        	",index="+_index+
+        	"]";
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/BufferDateCache.java b/src/java/org/eclipse/jetty/io/BufferDateCache.java
new file mode 100644
index 0000000..d92d57f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/BufferDateCache.java
@@ -0,0 +1,61 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.text.DateFormatSymbols;
+import java.util.Locale;
+
+import org.eclipse.jetty.util.DateCache;
+
+public class BufferDateCache extends DateCache
+{
+    Buffer _buffer;
+    String _last;
+    
+    public BufferDateCache()
+    {
+        super();
+    }
+
+    public BufferDateCache(String format, DateFormatSymbols s)
+    {
+        super(format,s);
+    }
+
+    public BufferDateCache(String format, Locale l)
+    {
+        super(format,l);
+    }
+
+    public BufferDateCache(String format)
+    {
+        super(format);
+    }
+
+    public synchronized Buffer formatBuffer(long date)
+    {
+        String d = super.format(date);
+        if (d==_last)
+            return _buffer;
+        _last=d;
+        _buffer=new ByteArrayBuffer(d);
+        
+        return _buffer;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/BufferUtil.java b/src/java/org/eclipse/jetty/io/BufferUtil.java
new file mode 100644
index 0000000..828d813
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/BufferUtil.java
@@ -0,0 +1,359 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.util.StringUtil;
+
+/* ------------------------------------------------------------------------------- */
+/** Buffer utility methods.
+ * 
+ * 
+ */
+public class BufferUtil
+{
+    static final byte SPACE= 0x20;
+    static final byte MINUS= '-';
+    static final byte[] DIGIT=
+    {(byte)'0',(byte)'1',(byte)'2',(byte)'3',(byte)'4',(byte)'5',(byte)'6',(byte)'7',(byte)'8',(byte)'9',(byte)'A',(byte)'B',(byte)'C',(byte)'D',(byte)'E',(byte)'F'};
+
+    /**
+     * Convert buffer to an integer.
+     * Parses up to the first non-numeric character. If no number is found an
+     * IllegalArgumentException is thrown
+     * @param buffer A buffer containing an integer. The position is not changed.
+     * @return an int 
+     */
+    public static int toInt(Buffer buffer)
+    {
+        int val= 0;
+        boolean started= false;
+        boolean minus= false;
+        for (int i= buffer.getIndex(); i < buffer.putIndex(); i++)
+        {
+            byte b= buffer.peek(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val= val * 10 + (b - '0');
+                started= true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus= true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(buffer.toString());
+    }
+    
+    /**
+     * Convert buffer to an long.
+     * Parses up to the first non-numeric character. If no number is found an
+     * IllegalArgumentException is thrown
+     * @param buffer A buffer containing an integer. The position is not changed.
+     * @return an int 
+     */
+    public static long toLong(Buffer buffer)
+    {
+        long val= 0;
+        boolean started= false;
+        boolean minus= false;
+        for (int i= buffer.getIndex(); i < buffer.putIndex(); i++)
+        {
+            byte b= buffer.peek(i);
+            if (b <= SPACE)
+            {
+                if (started)
+                    break;
+            }
+            else if (b >= '0' && b <= '9')
+            {
+                val= val * 10L + (b - '0');
+                started= true;
+            }
+            else if (b == MINUS && !started)
+            {
+                minus= true;
+            }
+            else
+                break;
+        }
+
+        if (started)
+            return minus ? (-val) : val;
+        throw new NumberFormatException(buffer.toString());
+    }
+
+    public static void putHexInt(Buffer buffer, int n)
+    {
+
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)(0x7f&'8'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                buffer.put((byte)(0x7f&'0'));
+                
+                return;
+            }
+            n= -n;
+        }
+
+        if (n < 0x10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started= false;
+            // This assumes constant time int arithmatic
+            for (int i= 0; i < hexDivisors.length; i++)
+            {
+                if (n < hexDivisors[i])
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started= true;
+                int d= n / hexDivisors[i];
+                buffer.put(DIGIT[d]);
+                n= n - d * hexDivisors[i];
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add hex integer BEFORE current getIndex.
+     * @param buffer
+     * @param n
+     */
+    public static void prependHexInt(Buffer buffer, int n)
+    {
+        if (n==0)
+        {
+            int gi=buffer.getIndex();
+            buffer.poke(--gi,(byte)'0');
+            buffer.setGetIndex(gi);
+        }
+        else
+        {
+            boolean minus=false;
+            if (n<0)
+            {
+                minus=true;
+                n=-n;
+            }
+
+            int gi=buffer.getIndex();
+            while(n>0)
+            {
+                int d = 0xf&n;
+                n=n>>4;
+                buffer.poke(--gi,DIGIT[d]);
+            }
+            
+            if (minus)
+                buffer.poke(--gi,(byte)'-');
+            buffer.setGetIndex(gi);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static void putDecInt(Buffer buffer, int n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Integer.MIN_VALUE)
+            {
+                buffer.put((byte)'2');
+                n= 147483648;
+            }
+            else
+                n= -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[n]);
+        }
+        else
+        {
+            boolean started= false;
+            // This assumes constant time int arithmatic
+            for (int i= 0; i < decDivisors.length; i++)
+            {
+                if (n < decDivisors[i])
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started= true;
+                int d= n / decDivisors[i];
+                buffer.put(DIGIT[d]);
+                n= n - d * decDivisors[i];
+            }
+        }
+    }
+    
+    public static void putDecLong(Buffer buffer, long n)
+    {
+        if (n < 0)
+        {
+            buffer.put((byte)'-');
+
+            if (n == Long.MIN_VALUE)
+            {
+                buffer.put((byte)'9');
+                n= 223372036854775808L;
+            }
+            else
+                n= -n;
+        }
+
+        if (n < 10)
+        {
+            buffer.put(DIGIT[(int)n]);
+        }
+        else
+        {
+            boolean started= false;
+            // This assumes constant time int arithmatic
+            for (int i= 0; i < decDivisorsL.length; i++)
+            {
+                if (n < decDivisorsL[i])
+                {
+                    if (started)
+                        buffer.put((byte)'0');
+                    continue;
+                }
+
+                started= true;
+                long d= n / decDivisorsL[i];
+                buffer.put(DIGIT[(int)d]);
+                n= n - d * decDivisorsL[i];
+            }
+        }
+    }
+    
+    public static Buffer toBuffer(long value)
+    {
+        ByteArrayBuffer buf=new ByteArrayBuffer(32);
+        putDecLong(buf, value);
+        return buf;
+    }
+
+    private final static int[] decDivisors=
+    { 
+        1000000000,
+        100000000,
+        10000000,
+        1000000,
+        100000,
+        10000,
+        1000,
+        100,
+        10,
+        1 
+    };
+
+    private final static int[] hexDivisors=
+    {
+        0x10000000,
+        0x1000000, 
+        0x100000, 
+        0x10000, 
+        0x1000, 
+        0x100, 
+        0x10, 
+        0x1 
+    };
+
+    private final static long[] decDivisorsL=
+    { 
+        1000000000000000000L,
+        100000000000000000L,
+        10000000000000000L,
+        1000000000000000L,
+        100000000000000L,
+        10000000000000L,
+        1000000000000L,
+        100000000000L,
+        10000000000L,
+        1000000000L,
+        100000000L,
+        10000000L,
+        1000000L,
+        100000L,
+        10000L,
+        1000L,
+        100L,
+        10L,
+        1L 
+    };
+
+
+    public static void putCRLF(Buffer buffer)
+    {
+        buffer.put((byte)13);
+        buffer.put((byte)10);
+    }
+    
+    public static boolean isPrefix(Buffer prefix,Buffer buffer)
+    {
+        if (prefix.length()>buffer.length())
+            return false;
+        int bi=buffer.getIndex();
+        for (int i=prefix.getIndex(); i<prefix.putIndex();i++)
+            if (prefix.peek(i)!=buffer.peek(bi++))
+                return false;
+        return true;
+    }
+
+    public static String to8859_1_String(Buffer buffer)
+    {
+        if (buffer instanceof CachedBuffer)
+            return buffer.toString();
+        return buffer.toString(StringUtil.__ISO_8859_1_CHARSET);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/Buffers.java b/src/java/org/eclipse/jetty/io/Buffers.java
new file mode 100644
index 0000000..5c0fb9d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/Buffers.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+/* ------------------------------------------------------------ */
+/** BufferSource.
+ * Represents a pool or other source of buffers and abstracts the creation
+ * of specific types of buffers (eg NIO).   The concept of big and little buffers
+ * is supported, but these terms have no absolute meaning and must be determined by context.
+ * 
+ */
+public interface Buffers
+{
+    enum Type { BYTE_ARRAY, DIRECT, INDIRECT } ;
+    
+    Buffer getHeader();
+    Buffer getBuffer();
+    Buffer getBuffer(int size);
+    
+    void returnBuffer(Buffer buffer);
+}
diff --git a/src/java/org/eclipse/jetty/io/BuffersFactory.java b/src/java/org/eclipse/jetty/io/BuffersFactory.java
new file mode 100644
index 0000000..2fc2818
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/BuffersFactory.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+public class BuffersFactory
+{
+    public static Buffers newBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize)
+    {
+        if (maxSize>=0)
+            return new PooledBuffers(headerType,headerSize,bufferType,bufferSize,otherType,maxSize);
+        return new ThreadLocalBuffers(headerType,headerSize,bufferType,bufferSize,otherType);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/ByteArrayBuffer.java b/src/java/org/eclipse/jetty/io/ByteArrayBuffer.java
new file mode 100644
index 0000000..1df36cd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/ByteArrayBuffer.java
@@ -0,0 +1,439 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * 
+ */
+public class ByteArrayBuffer extends AbstractBuffer
+{
+    // Set a maximum size to a write for the writeTo method, to ensure that very large content is not
+    // written as a single write (which may fall foul to write timeouts if consumed slowly).
+    final static int MAX_WRITE=Integer.getInteger("org.eclipse.jetty.io.ByteArrayBuffer.MAX_WRITE",128*1024);
+    final protected byte[] _bytes;
+
+    protected ByteArrayBuffer(int size, int access, boolean isVolatile)
+    {
+        this(new byte[size],0,0,access, isVolatile);
+    }
+    
+    public ByteArrayBuffer(byte[] bytes)
+    {
+        this(bytes, 0, bytes.length, READWRITE);
+    }
+
+    public ByteArrayBuffer(byte[] bytes, int index, int length)
+    {
+        this(bytes, index, length, READWRITE);
+    }
+
+    public ByteArrayBuffer(byte[] bytes, int index, int length, int access)
+    {
+        super(READWRITE, NON_VOLATILE);
+        _bytes = bytes;
+        setPutIndex(index + length);
+        setGetIndex(index);
+        _access = access;
+    }
+
+    public ByteArrayBuffer(byte[] bytes, int index, int length, int access, boolean isVolatile)
+    {
+        super(READWRITE, isVolatile);
+        _bytes = bytes;
+        setPutIndex(index + length);
+        setGetIndex(index);
+        _access = access;
+    }
+
+    public ByteArrayBuffer(int size)
+    {
+        this(new byte[size], 0, 0, READWRITE);
+        setPutIndex(0);
+    }
+
+    public ByteArrayBuffer(String value)
+    {
+        super(READWRITE,NON_VOLATILE);
+        _bytes = StringUtil.getBytes(value);
+        setGetIndex(0);
+        setPutIndex(_bytes.length);
+        _access=IMMUTABLE;
+        _string = value;
+    }
+    
+    public ByteArrayBuffer(String value,boolean immutable)
+    {
+        super(READWRITE,NON_VOLATILE);
+        _bytes = StringUtil.getBytes(value);
+        setGetIndex(0);
+        setPutIndex(_bytes.length);
+        if (immutable)
+        {
+            _access=IMMUTABLE;
+            _string = value;
+        }
+    }
+
+    public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
+    {
+        super(READWRITE,NON_VOLATILE);
+        _bytes = value.getBytes(encoding);
+        setGetIndex(0);
+        setPutIndex(_bytes.length);
+        _access=IMMUTABLE;
+        _string = value;
+    }
+
+    public byte[] array()
+    {
+        return _bytes;
+    }
+
+    public int capacity()
+    {
+        return _bytes.length;
+    }
+    
+    @Override
+    public void compact()
+    {
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        int s = markIndex() >= 0 ? markIndex() : getIndex();
+        if (s > 0)
+        {
+            int length = putIndex() - s;
+            if (length > 0)
+            {
+                System.arraycopy(_bytes, s,_bytes, 0, length);
+            }
+            if (markIndex() > 0) setMarkIndex(markIndex() - s);
+            setGetIndex(getIndex() - s);
+            setPutIndex(putIndex() - s);
+        }
+    }
+
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj==this)
+            return true;
+
+        if (obj == null || !(obj instanceof Buffer)) 
+            return false;
+        
+        if (obj instanceof Buffer.CaseInsensitve)
+            return equalsIgnoreCase((Buffer)obj);
+        
+
+        Buffer b = (Buffer) obj;
+        
+        // reject different lengths
+        if (b.length() != length()) 
+            return false;
+
+        // reject AbstractBuffer with different hash value
+        if (_hash != 0 && obj instanceof AbstractBuffer)
+        {
+            AbstractBuffer ab = (AbstractBuffer) obj;
+            if (ab._hash != 0 && _hash != ab._hash) 
+                return false;
+        }
+
+        // Nothing for it but to do the hard grind.
+        int get=getIndex();
+        int bi=b.putIndex();
+        for (int i = putIndex(); i-->get;)
+        {
+            byte b1 = _bytes[i];
+            byte b2 = b.peek(--bi);
+            if (b1 != b2) return false;
+        }
+        return true;
+    }
+
+
+    @Override
+    public boolean equalsIgnoreCase(Buffer b)
+    {
+        if (b==this)
+            return true;
+        
+        // reject different lengths
+        if (b==null || b.length() != length()) 
+            return false;
+
+        // reject AbstractBuffer with different hash value
+        if (_hash != 0 && b instanceof AbstractBuffer)
+        {
+            AbstractBuffer ab = (AbstractBuffer) b;
+            if (ab._hash != 0 && _hash != ab._hash) return false;
+        }
+
+        // Nothing for it but to do the hard grind.
+        int get=getIndex();
+        int bi=b.putIndex();
+        byte[] barray=b.array();
+        if (barray==null)
+        {
+            for (int i = putIndex(); i-->get;)
+            {
+                byte b1 = _bytes[i];
+                byte b2 = b.peek(--bi);
+                if (b1 != b2)
+                {
+                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+                    if (b1 != b2) return false;
+                }
+            }
+        }
+        else
+        {
+            for (int i = putIndex(); i-->get;)
+            {
+                byte b1 = _bytes[i];
+                byte b2 = barray[--bi];
+                if (b1 != b2)
+                {
+                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
+                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
+                    if (b1 != b2) return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public byte get()
+    {
+        return _bytes[_get++];
+    }
+
+    @Override
+    public int hashCode()
+    {
+        if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) 
+        {
+            int get=getIndex();
+            for (int i = putIndex(); i-- >get;)
+            {
+                byte b = _bytes[i];
+                if ('a' <= b && b <= 'z') 
+                    b = (byte) (b - 'a' + 'A');
+                _hash = 31 * _hash + b;
+            }
+            if (_hash == 0) 
+                _hash = -1;
+            _hashGet=_get;
+            _hashPut=_put;
+        }
+        return _hash;
+    }
+    
+    
+    public byte peek(int index)
+    {
+        return _bytes[index];
+    }
+    
+    public int peek(int index, byte[] b, int offset, int length)
+    {
+        int l = length;
+        if (index + l > capacity())
+        {
+            l = capacity() - index;
+            if (l==0)
+                return -1;
+        }
+        
+        if (l < 0) 
+            return -1;
+        
+        System.arraycopy(_bytes, index, b, offset, l);
+        return l;
+    }
+
+    public void poke(int index, byte b)
+    {
+        /* 
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        
+        if (index < 0) 
+            throw new IllegalArgumentException("index<0: " + index + "<0");
+        if (index > capacity())
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+        */
+        _bytes[index] = b;
+    }
+    
+    @Override
+    public int poke(int index, Buffer src)
+    {
+        _hash=0;
+        
+        /* 
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        if (index < 0) 
+            throw new IllegalArgumentException("index<0: " + index + "<0");
+        */
+        
+        int length=src.length();
+        if (index + length > capacity())
+        {
+            length=capacity()-index;
+            /*
+            if (length<0)
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+            */
+        }
+        
+        byte[] src_array = src.array();
+        if (src_array != null)
+            System.arraycopy(src_array, src.getIndex(), _bytes, index, length);
+        else 
+        {
+            int s=src.getIndex();
+            for (int i=0;i<length;i++)
+                _bytes[index++]=src.peek(s++);
+        }
+        
+        return length;
+    }
+    
+
+    @Override
+    public int poke(int index, byte[] b, int offset, int length)
+    {
+        _hash=0;
+        /*
+        if (isReadOnly()) 
+            throw new IllegalStateException(__READONLY);
+        if (index < 0) 
+            throw new IllegalArgumentException("index<0: " + index + "<0");
+        */
+        
+        if (index + length > capacity())
+        {
+            length=capacity()-index;
+            /* if (length<0)
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+            */
+        }
+        
+        System.arraycopy(b, offset, _bytes, index, length);
+        
+        return length;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void writeTo(OutputStream out)
+        throws IOException
+    {
+        int len=length();
+        if (MAX_WRITE>0 && len>MAX_WRITE)
+        {
+            int off=getIndex();
+            while(len>0)
+            {
+                int c=len>MAX_WRITE?MAX_WRITE:len;
+                out.write(_bytes,off,c);
+                off+=c;
+                len-=c;
+            }
+        }
+        else
+            out.write(_bytes,getIndex(),len);
+        if (!isImmutable())
+            clear();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public int readFrom(InputStream in,int max) throws IOException
+    {
+        if (max<0||max>space())
+            max=space();
+        int p = putIndex();
+        
+        int len=0, total=0, available=max;
+        while (total<max) 
+        {
+            len=in.read(_bytes,p,available);
+            if (len<0)
+                break;
+            else if (len>0)
+            {
+                p += len;
+                total += len;
+                available -= len;
+                setPutIndex(p);
+            }
+            if (in.available()<=0)
+                break;
+        }
+        if (len<0 && total==0)
+            return -1;
+        return total;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int space()
+    {
+        return _bytes.length - _put;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class CaseInsensitive extends ByteArrayBuffer implements Buffer.CaseInsensitve
+    {
+        public CaseInsensitive(String s)
+        {
+            super(s);
+        }
+        
+        public CaseInsensitive(byte[] b, int o, int l, int rw)
+        {
+            super(b,o,l,rw);
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            return obj instanceof Buffer && equalsIgnoreCase((Buffer)obj);
+        }
+        
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/ByteArrayEndPoint.java b/src/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
new file mode 100644
index 0000000..49bdfda
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/ByteArrayEndPoint.java
@@ -0,0 +1,408 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+
+
+/* ------------------------------------------------------------ */
+/** ByteArrayEndPoint.
+ *
+ *
+ */
+public class ByteArrayEndPoint implements ConnectedEndPoint
+{
+    protected byte[] _inBytes;
+    protected ByteArrayBuffer _in;
+    protected ByteArrayBuffer _out;
+    protected boolean _closed;
+    protected boolean _nonBlocking;
+    protected boolean _growOutput;
+    protected Connection _connection;
+    protected int _maxIdleTime;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.ConnectedEndPoint#getConnection()
+     */
+    public Connection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.ConnectedEndPoint#setConnection(org.eclipse.jetty.io.Connection)
+     */
+    public void setConnection(Connection connection)
+    {
+        _connection=connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the nonBlocking
+     */
+    public boolean isNonBlocking()
+    {
+        return _nonBlocking;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param nonBlocking the nonBlocking to set
+     */
+    public void setNonBlocking(boolean nonBlocking)
+    {
+        _nonBlocking=nonBlocking;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ByteArrayEndPoint(byte[] input, int outputSize)
+    {
+        _inBytes=input;
+        _in=new ByteArrayBuffer(input);
+        _out=new ByteArrayBuffer(outputSize);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the in.
+     */
+    public ByteArrayBuffer getIn()
+    {
+        return _in;
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param in The in to set.
+     */
+    public void setIn(ByteArrayBuffer in)
+    {
+        _in = in;
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the out.
+     */
+    public ByteArrayBuffer getOut()
+    {
+        return _out;
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param out The out to set.
+     */
+    public void setOut(ByteArrayBuffer out)
+    {
+        _out = out;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#isOpen()
+     */
+    public boolean isOpen()
+    {
+        return !_closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *  @see org.eclipse.jetty.io.EndPoint#isInputShutdown()
+     */
+    public boolean isInputShutdown()
+    {
+        return _closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *  @see org.eclipse.jetty.io.EndPoint#isOutputShutdown()
+     */
+    public boolean isOutputShutdown()
+    {
+        return _closed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#isBlocking()
+     */
+    public boolean isBlocking()
+    {
+        return !_nonBlocking;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean blockReadable(long millisecs)
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean blockWritable(long millisecs)
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#shutdownOutput()
+     */
+    public void shutdownOutput() throws IOException
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#shutdownInput()
+     */
+    public void shutdownInput() throws IOException
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    public void close() throws IOException
+    {
+        _closed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+     */
+    public int fill(Buffer buffer) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+
+        if (_in!=null && _in.length()>0)
+        {
+            int len = buffer.put(_in);
+            _in.skip(len);
+            return len;
+        }
+
+        if (_in!=null && _in.length()==0 && _nonBlocking)
+            return 0;
+
+        close();
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer buffer) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+        if (_growOutput && buffer.length()>_out.space())
+        {
+            _out.compact();
+
+            if (buffer.length()>_out.space())
+            {
+                ByteArrayBuffer n = new ByteArrayBuffer(_out.putIndex()+buffer.length());
+
+                n.put(_out.peek(0,_out.putIndex()));
+                if (_out.getIndex()>0)
+                {
+                    n.mark();
+                    n.setGetIndex(_out.getIndex());
+                }
+                _out=n;
+            }
+        }
+        int len = _out.put(buffer);
+        if (!buffer.isImmutable())
+            buffer.skip(len);
+        return len;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+    {
+        if (_closed)
+            throw new IOException("CLOSED");
+
+        int flushed=0;
+
+        if (header!=null && header.length()>0)
+            flushed=flush(header);
+
+        if (header==null || header.length()==0)
+        {
+            if (buffer!=null && buffer.length()>0)
+                flushed+=flush(buffer);
+
+            if (buffer==null || buffer.length()==0)
+            {
+                if (trailer!=null && trailer.length()>0)
+                {
+                    flushed+=flush(trailer);
+                }
+            }
+        }
+
+        return flushed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public void reset()
+    {
+        _closed=false;
+        _in.clear();
+        _out.clear();
+        if (_inBytes!=null)
+            _in.setPutIndex(_inBytes.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalAddr()
+     */
+    public String getLocalAddr()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalHost()
+     */
+    public String getLocalHost()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalPort()
+     */
+    public int getLocalPort()
+    {
+        return 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteAddr()
+     */
+    public String getRemoteAddr()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteHost()
+     */
+    public String getRemoteHost()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemotePort()
+     */
+    public int getRemotePort()
+    {
+        return 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    public Object getTransport()
+    {
+        return _inBytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flush() throws IOException
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the growOutput
+     */
+    public boolean isGrowOutput()
+    {
+        return _growOutput;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param growOutput the growOutput to set
+     */
+    public void setGrowOutput(boolean growOutput)
+    {
+        _growOutput=growOutput;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.EndPoint#getMaxIdleTime()
+     */
+    public int getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.EndPoint#setMaxIdleTime(int)
+     */
+    public void setMaxIdleTime(int timeMs) throws IOException
+    {
+        _maxIdleTime=timeMs;
+    }
+
+
+}
diff --git a/src/java/org/eclipse/jetty/io/ConnectedEndPoint.java b/src/java/org/eclipse/jetty/io/ConnectedEndPoint.java
new file mode 100644
index 0000000..b3f9324
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/ConnectedEndPoint.java
@@ -0,0 +1,25 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+public interface ConnectedEndPoint extends EndPoint
+{
+    Connection getConnection();
+    void setConnection(Connection connection);
+}
diff --git a/src/java/org/eclipse/jetty/io/Connection.java b/src/java/org/eclipse/jetty/io/Connection.java
new file mode 100644
index 0000000..b0c5349
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/Connection.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+/* ------------------------------------------------------------ */
+/** Abstract Connection used by Jetty Connectors.
+ * <p>
+ * Jetty will call the handle method of a connection when there is work
+ * to be done on the connection.  For blocking connections, this is soon
+ * as the connection is open and handle will keep being called until the
+ * connection is closed.   For non-blocking connections, handle will only
+ * be called if there are bytes to be read or the connection becomes writable
+ * after being write blocked.
+ *
+ * @see org.eclipse.jetty.io.nio.SelectorManager
+ */
+public interface Connection
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Handle the connection.
+     * @return The Connection to use for the next handling of the connection.
+     * This allows protocol upgrades and support for CONNECT.
+     * @throws IOException if the handling of I/O operations fail
+     */
+    Connection handle() throws IOException;
+
+    /**
+     * @return the timestamp at which the connection was created
+     */
+    long getTimeStamp();
+
+    /**
+     * @return whether this connection is idle, that is not parsing and not generating
+     * @see #onIdleExpired(long)
+     */
+    boolean isIdle();
+
+    /**
+     * <p>The semantic of this method is to return true to indicate interest in further reads,
+     * or false otherwise, but it is misnamed and should be really called <code>isReadInterested()</code>.</p>
+     *
+     * @return true to indicate interest in further reads, false otherwise
+     */
+    // TODO: rename to isReadInterested() in the next release
+    boolean isSuspended();
+
+    /**
+     * Called after the connection is closed
+     */
+    void onClose();
+
+    /**
+     * Called when the connection idle timeout expires
+     * @param idleForMs how long the connection has been idle
+     * @see #isIdle()
+     */
+    void onIdleExpired(long idleForMs);
+}
diff --git a/src/java/org/eclipse/jetty/io/EndPoint.java b/src/java/org/eclipse/jetty/io/EndPoint.java
new file mode 100644
index 0000000..c264587
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/EndPoint.java
@@ -0,0 +1,175 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+
+
+/**
+ *
+ * A transport EndPoint
+ */
+public interface EndPoint
+{
+    /**
+     * Shutdown any backing output stream associated with the endpoint
+     */
+    void shutdownOutput() throws IOException;
+
+    boolean isOutputShutdown();
+
+    /**
+     * Shutdown any backing input stream associated with the endpoint
+     */
+    void shutdownInput() throws IOException;
+
+    boolean isInputShutdown();
+
+    /**
+     * Close any backing stream associated with the endpoint
+     */
+    void close() throws IOException;
+
+    /**
+     * Fill the buffer from the current putIndex to it's capacity from whatever
+     * byte source is backing the buffer. The putIndex is increased if bytes filled.
+     * The buffer may chose to do a compact before filling.
+     * @return an <code>int</code> value indicating the number of bytes
+     * filled or -1 if EOF is reached.
+     * @throws EofException If input is shutdown or the endpoint is closed.
+     */
+    int fill(Buffer buffer) throws IOException;
+
+
+    /**
+     * Flush the buffer from the current getIndex to it's putIndex using whatever byte
+     * sink is backing the buffer. The getIndex is updated with the number of bytes flushed.
+     * Any mark set is cleared.
+     * If the entire contents of the buffer are flushed, then an implicit empty() is done.
+     *
+     * @param buffer The buffer to flush. This buffers getIndex is updated.
+     * @return  the number of bytes written
+     * @throws EofException If the endpoint is closed or output is shutdown.
+     */
+    int flush(Buffer buffer) throws IOException;
+
+    /**
+     * Flush the buffer from the current getIndex to it's putIndex using whatever byte
+     * sink is backing the buffer. The getIndex is updated with the number of bytes flushed.
+     * Any mark set is cleared.
+     * If the entire contents of the buffer are flushed, then an implicit empty() is done.
+     * The passed header/trailer buffers are written before/after the contents of this buffer. This may be done
+     * either as gather writes, as a poke into this buffer or as several writes. The implementation is free to
+     * select the optimal mechanism.
+     * @param header A buffer to write before flushing this buffer. This buffers getIndex is updated.
+     * @param buffer The buffer to flush. This buffers getIndex is updated.
+     * @param trailer A buffer to write after flushing this buffer. This buffers getIndex is updated.
+     * @return the total number of bytes written.
+     */
+    int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The local IP address to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public String getLocalAddr();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The local host name to which this <code>EndPoint</code> is bound, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public String getLocalHost();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The local port number on which this <code>EndPoint</code> is listening, or <code>0</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public int getLocalPort();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The remote IP address to which this <code>EndPoint</code> is connected, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public String getRemoteAddr();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The host name of the remote machine to which this <code>EndPoint</code> is connected, or <code>null</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public String getRemoteHost();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The remote port number to which this <code>EndPoint</code> is connected, or <code>0</code>
+     * if this <code>EndPoint</code> does not represent a network connection.
+     */
+    public int getRemotePort();
+
+    /* ------------------------------------------------------------ */
+    public boolean isBlocking();
+
+    /* ------------------------------------------------------------ */
+    public boolean blockReadable(long millisecs) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    public boolean blockWritable(long millisecs) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    public boolean isOpen();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The underlying transport object (socket, channel, etc.)
+     */
+    public Object getTransport();
+
+    /* ------------------------------------------------------------ */
+    /** Flush any buffered output.
+     * May fail to write all data if endpoint is non-blocking
+     * @throws EofException If the endpoint is closed or output is shutdown.
+     */
+    public void flush() throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /** Get the max idle time in ms.
+     * <p>The max idle time is the time the endpoint can be idle before
+     * extraordinary handling takes place.  This loosely corresponds to
+     * the {@link java.net.Socket#getSoTimeout()} for blocking connections,
+     * but {@link AsyncEndPoint} implementations must use other mechanisms
+     * to implement the max idle time.
+     * @return the max idle time in ms or if ms <= 0 implies an infinite timeout
+     */
+    public int getMaxIdleTime();
+
+    /* ------------------------------------------------------------ */
+    /** Set the max idle time.
+     * @param timeMs the max idle time in MS. Timeout <= 0 implies an infinite timeout
+     * @throws IOException if the timeout cannot be set.
+     */
+    public void setMaxIdleTime(int timeMs) throws IOException;
+
+
+
+}
diff --git a/src/java/org/eclipse/jetty/io/EofException.java b/src/java/org/eclipse/jetty/io/EofException.java
new file mode 100644
index 0000000..72042f4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/EofException.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.EOFException;
+
+
+/* ------------------------------------------------------------ */
+/** A Jetty specialization of EOFException.
+ * <p> This is thrown by Jetty to distinguish between EOF received from 
+ * the connection, vs and EOF thrown by some application talking to some other file/socket etc.
+ * The only difference in handling is that Jetty EOFs are logged less verbosely.
+ */
+public class EofException extends EOFException
+{
+    public EofException()
+    {
+    }
+    
+    public EofException(String reason)
+    {
+        super(reason);
+    }
+    
+    public EofException(Throwable th)
+    {
+        if (th!=null)
+            initCause(th);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/src/java/org/eclipse/jetty/io/NetworkTrafficListener.java
new file mode 100644
index 0000000..200574d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -0,0 +1,99 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.net.Socket;
+
+/**
+ * <p>A listener for raw network traffic within Jetty.</p>
+ * <p>{@link NetworkTrafficListener}s can be installed in a
+ * <code>org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector</code>,
+ * and are notified of the following network traffic events:</p>
+ * <ul>
+ * <li>Connection opened, when the server has accepted the connection from a remote client</li>
+ * <li>Incoming bytes, when the server receives bytes sent from a remote client</li>
+ * <li>Outgoing bytes, when the server sends bytes to a remote client</li>
+ * <li>Connection closed, when the server has closed the connection to a remote client</li>
+ * </ul>
+ * <p>{@link NetworkTrafficListener}s can be used to log the network traffic viewed by
+ * a Jetty server (for example logging to filesystem) for activities such as debugging
+ * or request/response cycles or for replaying request/response cycles to other servers.</p>
+ */
+public interface NetworkTrafficListener
+{
+    /**
+     * <p>Callback method invoked when a connection from a remote client has been accepted.</p>
+     * <p>The {@code socket} parameter can be used to extract socket address information of
+     * the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     */
+    public void opened(Socket socket);
+
+    /**
+     * <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the incoming bytes
+     */
+    public void incoming(Socket socket, Buffer bytes);
+
+    /**
+     * <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
+     * <p>This method is invoked after the bytes have been actually written to the remote client.</p>
+     *
+     * @param socket the socket associated with the remote client
+     * @param bytes  the read-only buffer containing the outgoing bytes
+     */
+    public void outgoing(Socket socket, Buffer bytes);
+
+    /**
+     * <p>Callback method invoked when a connection to a remote client has been closed.</p>
+     * <p>The {@code socket} parameter is already closed when this method is called, so it
+     * cannot be queried for socket address information of the remote client.<br />
+     * However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
+     * so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
+     * in this method.
+     *
+     * @param socket the (closed) socket associated with the remote client
+     */
+    public void closed(Socket socket);
+
+    /**
+     * <p>A commodity class that implements {@link NetworkTrafficListener} with empty methods.</p>
+     */
+    public static class Empty implements NetworkTrafficListener
+    {
+        public void opened(Socket socket)
+        {
+        }
+
+        public void incoming(Socket socket, Buffer bytes)
+        {
+        }
+
+        public void outgoing(Socket socket, Buffer bytes)
+        {
+        }
+
+        public void closed(Socket socket)
+        {
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/PooledBuffers.java b/src/java/org/eclipse/jetty/io/PooledBuffers.java
new file mode 100644
index 0000000..590b026
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/PooledBuffers.java
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class PooledBuffers extends AbstractBuffers
+{
+    private final Queue<Buffer> _headers;
+    private final Queue<Buffer> _buffers;
+    private final Queue<Buffer> _others;
+    private final AtomicInteger _size = new AtomicInteger();
+    private final int _maxSize;
+    private final boolean _otherHeaders;
+    private final boolean _otherBuffers;
+
+    /* ------------------------------------------------------------ */
+    public PooledBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType,int maxSize)
+    {
+        super(headerType,headerSize,bufferType,bufferSize,otherType);
+        _headers=new ConcurrentLinkedQueue<Buffer>();
+        _buffers=new ConcurrentLinkedQueue<Buffer>();
+        _others=new ConcurrentLinkedQueue<Buffer>();
+        _otherHeaders=headerType==otherType;
+        _otherBuffers=bufferType==otherType;
+        _maxSize=maxSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getHeader()
+    {
+        Buffer buffer = _headers.poll();
+        if (buffer==null)
+            buffer=newHeader();
+        else
+            _size.decrementAndGet();
+        return buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        Buffer buffer = _buffers.poll();
+        if (buffer==null)
+            buffer=newBuffer();
+        else
+            _size.decrementAndGet();
+        return buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer(int size)
+    {
+        if (_otherHeaders && size==getHeaderSize())
+            return getHeader();
+        if (_otherBuffers && size==getBufferSize())
+            return getBuffer();
+
+        // Look for an other buffer
+        Buffer buffer = _others.poll();
+
+        // consume all other buffers until one of the right size is found
+        while (buffer!=null && buffer.capacity()!=size)
+        {
+            _size.decrementAndGet();
+            buffer = _others.poll();
+        }
+
+        if (buffer==null)
+            buffer=newBuffer(size);
+        else
+            _size.decrementAndGet();
+        return buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void returnBuffer(Buffer buffer)
+    {
+        buffer.clear();
+        if (buffer.isVolatile() || buffer.isImmutable())
+            return;
+
+        if (_size.incrementAndGet() > _maxSize)
+            _size.decrementAndGet();
+        else
+        {
+            if (isHeader(buffer))
+                _headers.add(buffer);
+            else if (isBuffer(buffer))
+                _buffers.add(buffer);
+            else
+                _others.add(buffer);
+        }
+    }
+
+    public String toString()
+    {
+        return String.format("%s [%d/%d@%d,%d/%d@%d,%d/%d@-]",
+                getClass().getSimpleName(),
+                _headers.size(),_maxSize,_headerSize,
+                _buffers.size(),_maxSize,_bufferSize,
+                _others.size(),_maxSize);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/RuntimeIOException.java b/src/java/org/eclipse/jetty/io/RuntimeIOException.java
new file mode 100644
index 0000000..88c33f7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/RuntimeIOException.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link java.io.Writer}
+ */
+public class RuntimeIOException extends RuntimeException
+{
+    public RuntimeIOException()
+    {
+        super();
+    }
+
+    public RuntimeIOException(String message)
+    {
+        super(message);
+    }
+
+    public RuntimeIOException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    public RuntimeIOException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/SimpleBuffers.java b/src/java/org/eclipse/jetty/io/SimpleBuffers.java
new file mode 100644
index 0000000..6371c04
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/SimpleBuffers.java
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/** SimpleBuffers.
+ * Simple implementation of Buffers holder.
+ * 
+ *
+ */
+public class SimpleBuffers implements Buffers
+{   
+    final Buffer _header;
+    final Buffer _buffer;
+    boolean _headerOut;
+    boolean _bufferOut;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
+    public SimpleBuffers(Buffer header, Buffer buffer)
+    {
+        _header=header;
+        _buffer=buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        synchronized(this)
+        {
+            if (_buffer!=null && !_bufferOut)
+            {
+                _bufferOut=true;
+                return _buffer;
+            }
+            
+            if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_headerOut)
+            {
+                _headerOut=true;
+                return _header;
+            }
+            
+            if (_buffer!=null)
+                return new ByteArrayBuffer(_buffer.capacity());
+            return new ByteArrayBuffer(4096);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getHeader()
+    {
+        synchronized(this)
+        {
+            if (_header!=null && !_headerOut)
+            {
+                _headerOut=true;
+                return _header;
+            }
+            
+            if (_buffer!=null && _header!=null && _header.capacity()==_buffer.capacity() && !_bufferOut)
+            {
+                _bufferOut=true;
+                return _buffer;
+            }
+            
+            if (_header!=null)
+                return new ByteArrayBuffer(_header.capacity());
+            return new ByteArrayBuffer(4096);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer(int size)
+    {
+        synchronized(this)
+        {
+            if (_header!=null && _header.capacity()==size)
+                return getHeader();
+            if (_buffer!=null && _buffer.capacity()==size)
+                return getBuffer();
+            return null;            
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void returnBuffer(Buffer buffer)
+    {
+        synchronized(this)
+        {
+            buffer.clear();
+            if (buffer==_header)
+                _headerOut=false;
+            if (buffer==_buffer)
+                _bufferOut=false;
+        }
+    }
+
+
+}
diff --git a/src/java/org/eclipse/jetty/io/ThreadLocalBuffers.java b/src/java/org/eclipse/jetty/io/ThreadLocalBuffers.java
new file mode 100644
index 0000000..065193c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/ThreadLocalBuffers.java
@@ -0,0 +1,135 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Buffer pool.
+ * simple unbounded pool of buffers for header, request and response sizes.
+ *
+ */
+public class ThreadLocalBuffers extends AbstractBuffers 
+{
+    /* ------------------------------------------------------------ */
+    private final ThreadLocal<ThreadBuffers> _buffers=new ThreadLocal<ThreadBuffers>()
+    {
+        @Override
+        protected ThreadBuffers initialValue()
+        {
+            return new ThreadBuffers();
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    public ThreadLocalBuffers(Buffers.Type headerType, int headerSize, Buffers.Type bufferType, int bufferSize, Buffers.Type otherType)
+    {
+        super(headerType,headerSize,bufferType,bufferSize,otherType);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        ThreadBuffers buffers = _buffers.get();
+        if (buffers._buffer!=null)
+        {
+            Buffer b=buffers._buffer;
+            buffers._buffer=null;
+            return b;
+        }
+
+        if (buffers._other!=null && isBuffer(buffers._other))
+        {
+            Buffer b=buffers._other;
+            buffers._other=null;
+            return b;
+        }
+
+        return newBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getHeader()
+    {
+        ThreadBuffers buffers = _buffers.get();
+        if (buffers._header!=null)
+        {
+            Buffer b=buffers._header;
+            buffers._header=null;
+            return b;
+        }
+
+        if (buffers._other!=null && isHeader(buffers._other))
+        {
+            Buffer b=buffers._other;
+            buffers._other=null;
+            return b;
+        }
+
+        return newHeader();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer(int size)
+    {
+        ThreadBuffers buffers = _buffers.get();
+        if (buffers._other!=null && buffers._other.capacity()==size)
+        {
+            Buffer b=buffers._other;
+            buffers._other=null;
+            return b;
+        }
+
+        return newBuffer(size);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void returnBuffer(Buffer buffer)
+    {
+        buffer.clear();
+        if (buffer.isVolatile() || buffer.isImmutable())
+            return;
+        
+        ThreadBuffers buffers = _buffers.get();
+        
+        if (buffers._header==null && isHeader(buffer))
+            buffers._header=buffer;
+        else if (buffers._buffer==null && isBuffer(buffer))
+            buffers._buffer=buffer;
+        else
+            buffers._other=buffer;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "{{"+getHeaderSize()+","+getBufferSize()+"}}";
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class ThreadBuffers
+    {
+        Buffer _buffer;
+        Buffer _header;
+        Buffer _other;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/UncheckedIOException.java b/src/java/org/eclipse/jetty/io/UncheckedIOException.java
new file mode 100644
index 0000000..0d8e791
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/UncheckedIOException.java
@@ -0,0 +1,48 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.io;
+
+/* ------------------------------------------------------------ */
+/**
+ * Subclass of {@link java.lang.RuntimeException} used to signal that there
+ * was an {@link java.io.IOException} thrown by underlying {@link UncheckedPrintWriter}
+ */
+public class UncheckedIOException extends RuntimeException
+{
+    public UncheckedIOException()
+    {
+        super();
+    }
+
+    public UncheckedIOException(String message)
+    {
+        super(message);
+    }
+
+    public UncheckedIOException(Throwable cause)
+    {
+        super(cause);
+    }
+
+    public UncheckedIOException(String message, Throwable cause)
+    {
+        super(message,cause);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/UncheckedPrintWriter.java b/src/java/org/eclipse/jetty/io/UncheckedPrintWriter.java
new file mode 100644
index 0000000..6898ddf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/UncheckedPrintWriter.java
@@ -0,0 +1,682 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A wrapper for the {@link java.io.PrintWriter} that re-throws the instances of
+ * {@link java.io.IOException} thrown by the underlying implementation of
+ * {@link java.io.Writer} as {@link RuntimeIOException} instances.
+ */
+public class UncheckedPrintWriter extends PrintWriter
+{
+    private static final Logger LOG = Log.getLogger(UncheckedPrintWriter.class);
+
+    private boolean _autoFlush = false;
+    private IOException _ioException;
+    private boolean _isClosed = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Line separator string. This is the value of the line.separator property
+     * at the moment that the stream was created.
+     */
+    private String _lineSeparator;
+
+    public UncheckedPrintWriter(Writer out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter.
+     * 
+     * @param out
+     *            A character-output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     */
+    public UncheckedPrintWriter(Writer out, boolean autoFlush)
+    {
+        super(out,autoFlush);
+        this._autoFlush = autoFlush;
+        this._lineSeparator = System.getProperty("line.separator");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter, without automatic line flushing, from an
+     * existing OutputStream. This convenience constructor creates the necessary
+     * intermediate OutputStreamWriter, which will convert characters into bytes
+     * using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out)
+    {
+        this(out,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new PrintWriter from an existing OutputStream. This convenience
+     * constructor creates the necessary intermediate OutputStreamWriter, which
+     * will convert characters into bytes using the default character encoding.
+     * 
+     * @param out
+     *            An output stream
+     * @param autoFlush
+     *            A boolean; if true, the println() methods will flush the
+     *            output buffer
+     * 
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public UncheckedPrintWriter(OutputStream out, boolean autoFlush)
+    {
+        this(new BufferedWriter(new OutputStreamWriter(out)),autoFlush);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean checkError()
+    {
+        return _ioException!=null || super.checkError();
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void setError(Throwable th)
+    {
+      
+        super.setError();
+
+        if (th instanceof IOException)
+            _ioException=(IOException)th;
+        else
+        {
+            _ioException=new IOException(String.valueOf(th));
+            _ioException.initCause(th);
+        }
+
+        LOG.debug(th);
+    }
+
+
+    @Override
+    protected void setError()
+    {
+        setError(new IOException());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Check to make sure that the stream has not been closed */
+    private void isOpen() throws IOException
+    {       
+        if (_ioException!=null)
+            throw new RuntimeIOException(_ioException); 
+        
+        if (_isClosed)
+            throw new IOException("Stream closed");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Flush the stream.
+     */
+    @Override
+    public void flush()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.flush();
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the stream.
+     */
+    @Override
+    public void close()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                out.close();
+                _isClosed = true;
+            }
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a single character.
+     * 
+     * @param c
+     *            int specifying a character to be written.
+     */
+    @Override
+    public void write(int c)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(c);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of an array of characters.
+     * 
+     * @param buf
+     *            Array of characters
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(char buf[], int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(buf,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write an array of characters. This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     * 
+     * @param buf
+     *            Array of characters to be written
+     */
+    @Override
+    public void write(char buf[])
+    { 
+        this.write(buf,0,buf.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a portion of a string.
+     * 
+     * @param s
+     *            A String
+     * @param off
+     *            Offset from which to start writing characters
+     * @param len
+     *            Number of characters to write
+     */
+    @Override
+    public void write(String s, int off, int len)
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(s,off,len);
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Write a string. This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     * 
+     * @param s
+     *            String to be written
+     */
+    @Override
+    public void write(String s)
+    {
+        this.write(s,0,s.length());
+    }
+
+    private void newLine()
+    {
+        try
+        {
+            synchronized (lock)
+            {
+                isOpen();
+                out.write(_lineSeparator);
+                if (_autoFlush)
+                    out.flush();
+            }
+        }
+        catch (InterruptedIOException x)
+        {
+            Thread.currentThread().interrupt();
+        }
+        catch (IOException ex)
+        {
+            setError(ex);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value. The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param b
+     *            The <code>boolean</code> to be printed
+     */
+    @Override
+    public void print(boolean b)
+    {
+        this.write(b?"true":"false");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character. The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param c
+     *            The <code>char</code> to be printed
+     */
+    @Override
+    public void print(char c)
+    {
+        this.write(c);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param i
+     *            The <code>int</code> to be printed
+     * @see java.lang.Integer#toString(int)
+     */
+    @Override
+    public void print(int i)
+    {
+        this.write(String.valueOf(i));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer. The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are written
+     * in exactly the manner of the <code>{@link #write(int)}</code> method.
+     * 
+     * @param l
+     *            The <code>long</code> to be printed
+     * @see java.lang.Long#toString(long)
+     */
+    @Override
+    public void print(long l)
+    {
+        this.write(String.valueOf(l));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number. The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param f
+     *            The <code>float</code> to be printed
+     * @see java.lang.Float#toString(float)
+     */
+    @Override
+    public void print(float f)
+    {
+        this.write(String.valueOf(f));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number. The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     * 
+     * @param d
+     *            The <code>double</code> to be printed
+     * @see java.lang.Double#toString(double)
+     */
+    @Override
+    public void print(double d)
+    {
+        this.write(String.valueOf(d));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters. The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param s
+     *            The array of chars to be printed
+     * 
+     * @throws NullPointerException
+     *             If <code>s</code> is <code>null</code>
+     */
+    @Override
+    public void print(char s[])
+    {
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a string. If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed. Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     * 
+     * @param s
+     *            The <code>String</code> to be printed
+     */
+    @Override
+    public void print(String s)
+    {
+        if (s == null)
+        {
+            s = "null";
+        }
+        this.write(s);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an object. The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     * 
+     * @param obj
+     *            The <code>Object</code> to be printed
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public void print(Object obj)
+    {
+        this.write(String.valueOf(obj));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Terminate the current line by writing the line separator string. The line
+     * separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     */
+    @Override
+    public void println()
+    {
+        this.newLine();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a boolean value and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>boolean</code> value to be printed
+     */
+    @Override
+    public void println(boolean x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a character and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>char</code> value to be printed
+     */
+    @Override
+    public void println(char x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+     * #println()}</code>.
+     * 
+     * @param x
+     *            the <code>int</code> value to be printed
+     */
+    @Override
+    public void println(int x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a long integer and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>long</code> value to be printed
+     */
+    @Override
+    public void println(long x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a floating-point number and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>float</code> value to be printed
+     */
+    @Override
+    public void println(float x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a double-precision floating-point number and then terminate the
+     * line. This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>double</code> value to be printed
+     */
+    /* ------------------------------------------------------------ */
+    @Override
+    public void println(double x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an array of characters and then terminate the line. This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the array of <code>char</code> values to be printed
+     */
+    @Override
+    public void println(char x[])
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print a String and then terminate the line. This method behaves as though
+     * it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>String</code> value to be printed
+     */
+    @Override
+    public void println(String x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Print an Object and then terminate the line. This method behaves as
+     * though it invokes <code>{@link #print(Object)}</code> and then
+     * <code>{@link #println()}</code>.
+     * 
+     * @param x
+     *            the <code>Object</code> value to be printed
+     */
+    @Override
+    public void println(Object x)
+    {
+        synchronized (lock)
+        {
+            this.print(x);
+            this.println();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/View.java b/src/java/org/eclipse/jetty/io/View.java
new file mode 100644
index 0000000..47a1b57
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/View.java
@@ -0,0 +1,251 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+/**
+ * A View on another buffer.  Allows operations that do not change the _content or
+ * indexes of the backing buffer.
+ * 
+ * 
+ * 
+ */
+public class View extends AbstractBuffer
+{
+    Buffer _buffer;
+
+    /**
+     * @param buffer The <code>Buffer</code> on which we are presenting a <code>View</code>.
+     * @param mark The initial value of the {@link Buffer#markIndex mark index}
+     * @param get The initial value of the {@link Buffer#getIndex get index}
+     * @param put The initial value of the {@link Buffer#putIndex put index}
+     * @param access The access level - one of the constants from {@link Buffer}.
+     */
+    public View(Buffer buffer, int mark, int get, int put,int access)
+    {
+        super(READWRITE,!buffer.isImmutable());
+        _buffer=buffer.buffer();
+        setPutIndex(put);
+        setGetIndex(get);
+        setMarkIndex(mark);
+        _access=access;
+    }
+    
+    public View(Buffer buffer)
+    {
+        super(READWRITE,!buffer.isImmutable());
+        _buffer=buffer.buffer();
+        setPutIndex(buffer.putIndex());
+        setGetIndex(buffer.getIndex());
+        setMarkIndex(buffer.markIndex());
+        _access=buffer.isReadOnly()?READONLY:READWRITE;
+    }
+
+    public View()
+    {
+        super(READWRITE,true);
+    }
+    
+    /**
+     * Update view to buffer
+     */
+    public void update(Buffer buffer)
+    {
+        _access=READWRITE;
+        _buffer=buffer.buffer();
+        setGetIndex(0);
+        setPutIndex(buffer.putIndex());
+        setGetIndex(buffer.getIndex());
+        setMarkIndex(buffer.markIndex());
+        _access=buffer.isReadOnly()?READONLY:READWRITE;
+    }
+
+    public void update(int get, int put)
+    {
+        int a=_access;
+        _access=READWRITE;
+        setGetIndex(0);
+        setPutIndex(put);
+        setGetIndex(get);
+        setMarkIndex(-1);
+        _access=a;
+    }
+
+    /**
+     * @return The {@link Buffer#array()} from the underlying buffer.
+     */
+    public byte[] array()
+    {
+        return _buffer.array();
+    }
+
+    /**
+     * @return The {@link Buffer#buffer()} from the underlying buffer.
+     */
+    @Override
+    public Buffer buffer()
+    {
+        return _buffer.buffer();
+    }
+
+    /**
+     * @return The {@link Buffer#capacity} of the underlying buffer.
+     */
+    public int capacity()
+    {
+        return _buffer.capacity();
+    }
+
+    /**
+     *  
+     */
+    @Override
+    public void clear()
+    {
+        setMarkIndex(-1);
+        setGetIndex(0);
+        setPutIndex(_buffer.getIndex());
+        setGetIndex(_buffer.getIndex());
+    }
+
+    /**
+     *  
+     */
+    @Override
+    public void compact()
+    {
+        // TODO
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        return  this==obj ||((obj instanceof Buffer)&& obj.equals(this)) || super.equals(obj);
+    }
+
+    /**
+     * @return Whether the underlying buffer is {@link Buffer#isReadOnly read only}
+     */
+    @Override
+    public boolean isReadOnly()
+    {
+        return _buffer.isReadOnly();
+    }
+
+    /**
+     * @return Whether the underlying buffer is {@link Buffer#isVolatile volatile}
+     */
+    @Override
+    public boolean isVolatile()
+    {
+        return true;
+    }
+
+    /**
+     * @return The result of calling {@link Buffer#peek(int)} on the underlying buffer
+     */
+    public byte peek(int index)
+    {
+        return _buffer.peek(index);
+    }
+
+    /**
+     * @return The result of calling {@link Buffer#peek(int, byte[], int, int)} on the underlying buffer
+     */
+    public int peek(int index, byte[] b, int offset, int length)
+    {
+        return _buffer.peek(index,b,offset,length);
+    }
+
+    /**
+     * @return The result of calling {@link Buffer#peek(int, int)} on the underlying buffer
+     */
+    @Override
+    public Buffer peek(int index, int length)
+    {
+        return _buffer.peek(index, length);
+    }
+    
+    /**
+     * @param index
+     * @param src
+     */
+    @Override
+    public int poke(int index, Buffer src)
+    {
+        return _buffer.poke(index,src); 
+    }
+
+    /**
+     * @param index
+     * @param b
+     */
+    public void poke(int index, byte b)
+    {
+        _buffer.poke(index,b);
+    }
+
+    /**
+     * @param index
+     * @param b
+     * @param offset
+     * @param length
+     */
+    @Override
+    public int poke(int index, byte[] b, int offset, int length)
+    {
+        return _buffer.poke(index,b,offset,length);
+    }
+    
+    @Override
+    public String toString()
+    {
+        if (_buffer==null)
+            return "INVALID";
+        return super.toString();
+    }
+    
+    public static class CaseInsensitive extends View implements Buffer.CaseInsensitve
+    {
+        public CaseInsensitive()
+        {
+            super();
+        }
+
+        public CaseInsensitive(Buffer buffer, int mark, int get, int put, int access)
+        {
+            super(buffer,mark,get,put,access);
+        }
+
+        public CaseInsensitive(Buffer buffer)
+        {
+            super(buffer);
+        }
+        
+        @Override
+        public boolean equals(Object obj)
+        {
+            return  this==obj ||((obj instanceof Buffer)&&((Buffer)obj).equalsIgnoreCase(this)) || super.equals(obj);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/WriterOutputStream.java b/src/java/org/eclipse/jetty/io/WriterOutputStream.java
new file mode 100644
index 0000000..620bbf8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/WriterOutputStream.java
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+
+/* ------------------------------------------------------------ */
+/** Wrap a Writer as an OutputStream.
+ * When all you have is a Writer and only an OutputStream will do.
+ * Try not to use this as it indicates that your design is a dogs
+ * breakfast (JSP made me write it).
+ * 
+ */
+public class WriterOutputStream extends OutputStream
+{
+    protected final Writer _writer;
+    protected final String _encoding;
+    private final byte[] _buf=new byte[1];
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer, String encoding)
+    {
+        _writer=writer;
+        _encoding=encoding;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public WriterOutputStream(Writer writer)
+    {
+        _writer=writer;
+        _encoding=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+        throws IOException
+    {
+        _writer.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+        throws IOException
+    {
+        _writer.flush();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b) 
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b));
+        else
+            _writer.write(new String(b,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len)
+        throws IOException
+    {
+        if (_encoding==null)
+            _writer.write(new String(b,off,len));
+        else
+            _writer.write(new String(b,off,len,_encoding));
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void write(int b)
+        throws IOException
+    {
+        _buf[0]=(byte)b;
+        write(_buf);
+    }
+}
+
diff --git a/src/java/org/eclipse/jetty/io/bio/SocketEndPoint.java b/src/java/org/eclipse/jetty/io/bio/SocketEndPoint.java
new file mode 100644
index 0000000..333ff9e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/bio/SocketEndPoint.java
@@ -0,0 +1,286 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.bio;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SocketEndPoint extends StreamEndPoint
+{
+    private static final Logger LOG = Log.getLogger(SocketEndPoint.class);
+
+    final Socket _socket;
+    final InetSocketAddress _local;
+    final InetSocketAddress _remote;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public SocketEndPoint(Socket socket)
+    	throws IOException
+    {
+        super(socket.getInputStream(),socket.getOutputStream());
+        _socket=socket;
+        _local=(InetSocketAddress)_socket.getLocalSocketAddress();
+        _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
+        super.setMaxIdleTime(_socket.getSoTimeout());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    protected SocketEndPoint(Socket socket, int maxIdleTime)
+        throws IOException
+    {
+        super(socket.getInputStream(),socket.getOutputStream());
+        _socket=socket;
+        _local=(InetSocketAddress)_socket.getLocalSocketAddress();
+        _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
+        _socket.setSoTimeout(maxIdleTime>0?maxIdleTime:0);
+        super.setMaxIdleTime(maxIdleTime);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see org.eclipse.io.BufferIO#isClosed()
+     */
+    @Override
+    public boolean isOpen()
+    {
+        return super.isOpen() && _socket!=null && !_socket.isClosed();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isInputShutdown()
+    {
+        if (_socket instanceof SSLSocket)
+            return super.isInputShutdown();
+        return _socket.isClosed() || _socket.isInputShutdown();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isOutputShutdown()
+    {
+        if (_socket instanceof SSLSocket)
+            return super.isOutputShutdown();
+
+        return _socket.isClosed() || _socket.isOutputShutdown();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    protected final void shutdownSocketOutput() throws IOException
+    {
+        if (!_socket.isClosed())
+        {
+            if (!_socket.isOutputShutdown())
+                _socket.shutdownOutput();
+            if (_socket.isInputShutdown())
+                _socket.close();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput()
+     */
+    @Override
+    public void shutdownOutput() throws IOException
+    {
+        if (_socket instanceof SSLSocket)
+            super.shutdownOutput();
+        else
+            shutdownSocketOutput();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public void shutdownSocketInput() throws IOException
+    {
+        if (!_socket.isClosed())
+        {
+            if (!_socket.isInputShutdown())
+                _socket.shutdownInput();
+            if (_socket.isOutputShutdown())
+                _socket.close();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.io.bio.StreamEndPoint#shutdownOutput()
+     */
+    @Override
+    public void shutdownInput() throws IOException
+    {
+        if (_socket instanceof SSLSocket)
+            super.shutdownInput();
+        else
+            shutdownSocketInput();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see org.eclipse.io.BufferIO#close()
+     */
+    @Override
+    public void close() throws IOException
+    {
+        _socket.close();
+        _in=null;
+        _out=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalAddr()
+     */
+    @Override
+    public String getLocalAddr()
+    {
+       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+           return StringUtil.ALL_INTERFACES;
+
+        return _local.getAddress().getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalHost()
+     */
+    @Override
+    public String getLocalHost()
+    {
+       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+           return StringUtil.ALL_INTERFACES;
+
+        return _local.getAddress().getCanonicalHostName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalPort()
+     */
+    @Override
+    public int getLocalPort()
+    {
+        if (_local==null)
+            return -1;
+        return _local.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteAddr()
+     */
+    @Override
+    public String getRemoteAddr()
+    {
+        if (_remote==null)
+            return null;
+        InetAddress addr = _remote.getAddress();
+        return ( addr == null ? null : addr.getHostAddress() );
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteHost()
+     */
+    @Override
+    public String getRemoteHost()
+    {
+        if (_remote==null)
+            return null;
+        return _remote.getAddress().getCanonicalHostName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemotePort()
+     */
+    @Override
+    public int getRemotePort()
+    {
+        if (_remote==null)
+            return -1;
+        return _remote.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    @Override
+    public Object getTransport()
+    {
+        return _socket;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int)
+     */
+    @Override
+    public void setMaxIdleTime(int timeMs) throws IOException
+    {
+        if (timeMs!=getMaxIdleTime())
+            _socket.setSoTimeout(timeMs>0?timeMs:0);
+        super.setMaxIdleTime(timeMs);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void idleExpired() throws IOException
+    {
+        try
+        {
+            if (!isInputShutdown())
+                shutdownInput();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+            _socket.close();
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return _local + " <--> " + _remote;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/bio/StreamEndPoint.java b/src/java/org/eclipse/jetty/io/bio/StreamEndPoint.java
new file mode 100644
index 0000000..b43ca5c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/bio/StreamEndPoint.java
@@ -0,0 +1,325 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.io.bio;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketTimeoutException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+
+public class StreamEndPoint implements EndPoint
+{
+    InputStream _in;
+    OutputStream _out;
+    int _maxIdleTime;
+    boolean _ishut;
+    boolean _oshut;
+
+    /**
+     *
+     */
+    public StreamEndPoint(InputStream in, OutputStream out)
+    {
+        _in=in;
+        _out=out;
+    }
+
+    public boolean isBlocking()
+    {
+        return true;
+    }
+
+    public boolean blockReadable(long millisecs) throws IOException
+    {
+        return true;
+    }
+
+    public boolean blockWritable(long millisecs) throws IOException
+    {
+        return true;
+    }
+
+    /*
+     * @see org.eclipse.io.BufferIO#isOpen()
+     */
+    public boolean isOpen()
+    {
+        return _in!=null;
+    }
+
+    /*
+     * @see org.eclipse.io.BufferIO#isOpen()
+     */
+    public final boolean isClosed()
+    {
+        return !isOpen();
+    }
+
+    public void shutdownOutput() throws IOException
+    {
+        _oshut = true;
+        if (_ishut && _out!=null)
+            _out.close();
+    }
+
+    public boolean isInputShutdown()
+    {
+        return _ishut;
+    }
+
+    public void shutdownInput() throws IOException
+    {
+        _ishut = true;
+        if (_oshut&&_in!=null)
+            _in.close();
+    }
+
+    public boolean isOutputShutdown()
+    {
+        return _oshut;
+    }
+
+    /*
+     * @see org.eclipse.io.BufferIO#close()
+     */
+    public void close() throws IOException
+    {
+        if (_in!=null)
+            _in.close();
+        _in=null;
+        if (_out!=null)
+            _out.close();
+        _out=null;
+    }
+
+    protected void idleExpired() throws IOException
+    {
+        if (_in!=null)
+            _in.close();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.BufferIO#fill(org.eclipse.io.Buffer)
+     */
+    public int fill(Buffer buffer) throws IOException
+    {
+        if (_ishut)
+            return -1;
+        if (_in==null)
+            return 0;
+
+        int space=buffer.space();
+        if (space<=0)
+        {
+            if (buffer.hasContent())
+                return 0;
+            throw new IOException("FULL");
+        }
+
+        try
+        {
+            int filled=buffer.readFrom(_in, space);
+            if (filled<0)
+                shutdownInput();
+            return filled;
+        }
+        catch(SocketTimeoutException e)
+        {
+            idleExpired();
+            return -1;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer buffer) throws IOException
+    {
+        if (_oshut)
+            return -1;
+        if (_out==null)
+            return 0;
+        int length=buffer.length();
+        if (length>0)
+            buffer.writeTo(_out);
+        if (!buffer.isImmutable())
+            buffer.clear();
+        return length;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.BufferIO#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+    {
+        int len=0;
+
+        if (header!=null)
+        {
+            int tw=header.length();
+            if (tw>0)
+            {
+                int f=flush(header);
+                len=f;
+                if (f<tw)
+                    return len;
+            }
+        }
+
+        if (buffer!=null)
+        {
+            int tw=buffer.length();
+            if (tw>0)
+            {
+                int f=flush(buffer);
+                if (f<0)
+                    return len>0?len:f;
+                len+=f;
+                if (f<tw)
+                    return len;
+            }
+        }
+
+        if (trailer!=null)
+        {
+            int tw=trailer.length();
+            if (tw>0)
+            {
+                int f=flush(trailer);
+                if (f<0)
+                    return len>0?len:f;
+                len+=f;
+            }
+        }
+        return len;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalAddr()
+     */
+    public String getLocalAddr()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalHost()
+     */
+    public String getLocalHost()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalPort()
+     */
+    public int getLocalPort()
+    {
+        return 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteAddr()
+     */
+    public String getRemoteAddr()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteHost()
+     */
+    public String getRemoteHost()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemotePort()
+     */
+    public int getRemotePort()
+    {
+        return 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    public Object getTransport()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public InputStream getInputStream()
+    {
+        return _in;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setInputStream(InputStream in)
+    {
+        _in=in;
+    }
+
+    /* ------------------------------------------------------------ */
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setOutputStream(OutputStream out)
+    {
+        _out=out;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void flush()
+        throws IOException
+    {
+        if (_out != null)
+            _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxIdleTime(int timeMs) throws IOException
+    {
+        _maxIdleTime=timeMs;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/io/bio/StringEndPoint.java b/src/java/org/eclipse/jetty/io/bio/StringEndPoint.java
new file mode 100644
index 0000000..a74768a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/bio/StringEndPoint.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.bio;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import org.eclipse.jetty.util.StringUtil;
+
+/**
+ * 
+ *
+ * To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Generation - Code and Comments
+ */
+public class StringEndPoint extends StreamEndPoint
+{
+    String _encoding=StringUtil.__UTF8;
+    ByteArrayInputStream _bin = new ByteArrayInputStream(new byte[0]);
+    ByteArrayOutputStream _bout = new ByteArrayOutputStream();
+    
+    public StringEndPoint()
+    {
+        super(null,null);
+        _in=_bin;
+        _out=_bout;
+    }
+    
+    public StringEndPoint(String encoding)
+    {
+        this();
+        if (encoding!=null)
+            _encoding=encoding;
+    }
+    
+    public void setInput(String s) 
+    {
+        try
+        {
+            byte[] bytes = s.getBytes(_encoding);
+            _bin=new ByteArrayInputStream(bytes);
+            _in=_bin;
+            _bout = new ByteArrayOutputStream();
+            _out=_bout;
+            _ishut=false;
+            _oshut=false;
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e.toString());
+        }
+    }
+    
+    public String getOutput() 
+    {
+        try
+        {
+            String s = new String(_bout.toByteArray(),_encoding);
+            _bout.reset();
+      	  return s;
+        }
+        catch(final Exception e)
+        {
+            throw new IllegalStateException(_encoding)
+            {
+                {initCause(e);}
+            };
+        }
+    }
+
+    /**
+     * @return <code>true</code> if there are bytes remaining to be read from the encoded input
+     */
+    public boolean hasMore()
+    {
+        return _bin.available()>0;
+    }   
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/AsyncConnection.java b/src/java/org/eclipse/jetty/io/nio/AsyncConnection.java
new file mode 100644
index 0000000..deab21b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/AsyncConnection.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Connection;
+
+public interface AsyncConnection extends Connection
+{
+    void onInputShutdown() throws IOException;
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java b/src/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java
new file mode 100644
index 0000000..0ee1c1e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java
@@ -0,0 +1,509 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Channel End Point.
+ * <p>Holds the channel and socket for an NIO endpoint.
+ *
+ */
+public class ChannelEndPoint implements EndPoint
+{
+    private static final Logger LOG = Log.getLogger(ChannelEndPoint.class);
+
+    protected final ByteChannel _channel;
+    protected final ByteBuffer[] _gather2=new ByteBuffer[2];
+    protected final Socket _socket;
+    protected final InetSocketAddress _local;
+    protected final InetSocketAddress _remote;
+    protected volatile int _maxIdleTime;
+    private volatile boolean _ishut;
+    private volatile boolean _oshut;
+
+    public ChannelEndPoint(ByteChannel channel) throws IOException
+    {
+        super();
+        this._channel = channel;
+        _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null;
+        if (_socket!=null)
+        {
+            _local=(InetSocketAddress)_socket.getLocalSocketAddress();
+            _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
+            _maxIdleTime=_socket.getSoTimeout();
+        }
+        else
+        {
+            _local=_remote=null;
+        }
+    }
+
+    protected ChannelEndPoint(ByteChannel channel, int maxIdleTime) throws IOException
+    {
+        this._channel = channel;
+        _maxIdleTime=maxIdleTime;
+        _socket=(channel instanceof SocketChannel)?((SocketChannel)channel).socket():null;
+        if (_socket!=null)
+        {
+            _local=(InetSocketAddress)_socket.getLocalSocketAddress();
+            _remote=(InetSocketAddress)_socket.getRemoteSocketAddress();
+            _socket.setSoTimeout(_maxIdleTime);
+        }
+        else
+        {
+            _local=_remote=null;
+        }
+    }
+
+    public boolean isBlocking()
+    {
+        return  !(_channel instanceof SelectableChannel) || ((SelectableChannel)_channel).isBlocking();
+    }
+
+    public boolean blockReadable(long millisecs) throws IOException
+    {
+        return true;
+    }
+
+    public boolean blockWritable(long millisecs) throws IOException
+    {
+        return true;
+    }
+
+    /*
+     * @see org.eclipse.io.EndPoint#isOpen()
+     */
+    public boolean isOpen()
+    {
+        return _channel.isOpen();
+    }
+
+    /** Shutdown the channel Input.
+     * Cannot be overridden. To override, see {@link #shutdownInput()}
+     * @throws IOException
+     */
+    protected final void shutdownChannelInput() throws IOException
+    {
+        LOG.debug("ishut {}", this);
+        _ishut = true;
+        if (_channel.isOpen())
+        {
+            if (_socket != null)
+            {
+                try
+                {
+                    if (!_socket.isInputShutdown())
+                    {
+                        _socket.shutdownInput();
+                    }
+                }
+                catch (SocketException e)
+                {
+                    LOG.debug(e.toString());
+                    LOG.ignore(e);
+                }
+                finally
+                {
+                    if (_oshut)
+                    {
+                        close();
+                    }
+                }
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    public void shutdownInput() throws IOException
+    {
+        shutdownChannelInput();
+    }
+
+    protected final void shutdownChannelOutput() throws IOException
+    {
+        LOG.debug("oshut {}",this);
+        _oshut = true;
+        if (_channel.isOpen())
+        {
+            if (_socket != null)
+            {
+                try
+                {
+                    if (!_socket.isOutputShutdown())
+                    {
+                        _socket.shutdownOutput();
+                    }
+                }
+                catch (SocketException e)
+                {
+                    LOG.debug(e.toString());
+                    LOG.ignore(e);
+                }
+                finally
+                {
+                    if (_ishut)
+                    {
+                        close();
+                    }
+                }
+            }
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    public void shutdownOutput() throws IOException
+    {
+        shutdownChannelOutput();
+    }
+
+    public boolean isOutputShutdown()
+    {
+        return _oshut || !_channel.isOpen() || _socket != null && _socket.isOutputShutdown();
+    }
+
+    public boolean isInputShutdown()
+    {
+        return _ishut || !_channel.isOpen() || _socket != null && _socket.isInputShutdown();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#close()
+     */
+    public void close() throws IOException
+    {
+        LOG.debug("close {}",this);
+        _channel.close();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#fill(org.eclipse.io.Buffer)
+     */
+    public int fill(Buffer buffer) throws IOException
+    {
+        if (_ishut)
+            return -1;
+        Buffer buf = buffer.buffer();
+        int len=0;
+        if (buf instanceof NIOBuffer)
+        {
+            final NIOBuffer nbuf = (NIOBuffer)buf;
+            final ByteBuffer bbuf=nbuf.getByteBuffer();
+
+            //noinspection SynchronizationOnLocalVariableOrMethodParameter
+            try
+            {
+                synchronized(bbuf)
+                {
+                    try
+                    {
+                        bbuf.position(buffer.putIndex());
+                        len=_channel.read(bbuf);
+                    }
+                    finally
+                    {
+                        buffer.setPutIndex(bbuf.position());
+                        bbuf.position(0);
+                    }
+                }
+
+                if (len<0 && isOpen())
+                {
+                    if (!isInputShutdown())
+                        shutdownInput();
+                    if (isOutputShutdown())
+                        _channel.close();
+                }
+            }
+            catch (IOException x)
+            {
+                LOG.debug("Exception while filling", x);
+                try
+                {
+                    if (_channel.isOpen())
+                        _channel.close();
+                }
+                catch (Exception xx)
+                {
+                    LOG.ignore(xx);
+                }
+
+                if (len>0)
+                    throw x;
+                len=-1;
+            }
+        }
+        else
+        {
+            throw new IOException("Not Implemented");
+        }
+
+        return len;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer buffer) throws IOException
+    {
+        Buffer buf = buffer.buffer();
+        int len=0;
+        if (buf instanceof NIOBuffer)
+        {
+            final NIOBuffer nbuf = (NIOBuffer)buf;
+            final ByteBuffer bbuf=nbuf.getByteBuffer().asReadOnlyBuffer();
+            try
+            {
+                bbuf.position(buffer.getIndex());
+                bbuf.limit(buffer.putIndex());
+                len=_channel.write(bbuf);
+            }
+            finally
+            {
+                if (len>0)
+                    buffer.skip(len);
+            }
+        }
+        else if (buf instanceof RandomAccessFileBuffer)
+        {
+            len = ((RandomAccessFileBuffer)buf).writeTo(_channel,buffer.getIndex(),buffer.length());
+            if (len>0)
+                buffer.skip(len);
+        }
+        else if (buffer.array()!=null)
+        {
+            ByteBuffer b = ByteBuffer.wrap(buffer.array(), buffer.getIndex(), buffer.length());
+            len=_channel.write(b);
+            if (len>0)
+                buffer.skip(len);
+        }
+        else
+        {
+            throw new IOException("Not Implemented");
+        }
+        return len;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.io.EndPoint#flush(org.eclipse.io.Buffer, org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+     */
+    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+    {
+        int length=0;
+
+        Buffer buf0 = header==null?null:header.buffer();
+        Buffer buf1 = buffer==null?null:buffer.buffer();
+
+        if (_channel instanceof GatheringByteChannel &&
+            header!=null && header.length()!=0 && buf0 instanceof NIOBuffer &&
+            buffer!=null && buffer.length()!=0 && buf1 instanceof NIOBuffer)
+        {
+            length = gatheringFlush(header,((NIOBuffer)buf0).getByteBuffer(),buffer,((NIOBuffer)buf1).getByteBuffer());
+        }
+        else
+        {
+            // flush header
+            if (header!=null && header.length()>0)
+                length=flush(header);
+
+            // flush buffer
+            if ((header==null || header.length()==0) &&
+                 buffer!=null && buffer.length()>0)
+                length+=flush(buffer);
+
+            // flush trailer
+            if ((header==null || header.length()==0) &&
+                (buffer==null || buffer.length()==0) &&
+                 trailer!=null && trailer.length()>0)
+                length+=flush(trailer);
+        }
+
+        return length;
+    }
+
+    protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException
+    {
+        int length;
+
+        synchronized(this)
+        {
+            // Adjust position indexs of buf0 and buf1
+            bbuf0=bbuf0.asReadOnlyBuffer();
+            bbuf0.position(header.getIndex());
+            bbuf0.limit(header.putIndex());
+            bbuf1=bbuf1.asReadOnlyBuffer();
+            bbuf1.position(buffer.getIndex());
+            bbuf1.limit(buffer.putIndex());
+
+            _gather2[0]=bbuf0;
+            _gather2[1]=bbuf1;
+
+            // do the gathering write.
+            length=(int)((GatheringByteChannel)_channel).write(_gather2);
+
+            int hl=header.length();
+            if (length>hl)
+            {
+                header.clear();
+                buffer.skip(length-hl);
+            }
+            else if (length>0)
+            {
+                header.skip(length);
+            }
+        }
+        return length;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the channel.
+     */
+    public ByteChannel getChannel()
+    {
+        return _channel;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalAddr()
+     */
+    public String getLocalAddr()
+    {
+        if (_socket==null)
+            return null;
+       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+           return StringUtil.ALL_INTERFACES;
+        return _local.getAddress().getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalHost()
+     */
+    public String getLocalHost()
+    {
+        if (_socket==null)
+            return null;
+       if (_local==null || _local.getAddress()==null || _local.getAddress().isAnyLocalAddress())
+           return StringUtil.ALL_INTERFACES;
+        return _local.getAddress().getCanonicalHostName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getLocalPort()
+     */
+    public int getLocalPort()
+    {
+        if (_socket==null)
+            return 0;
+        if (_local==null)
+            return -1;
+        return _local.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteAddr()
+     */
+    public String getRemoteAddr()
+    {
+        if (_socket==null)
+            return null;
+        if (_remote==null)
+            return null;
+        return _remote.getAddress().getHostAddress();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemoteHost()
+     */
+    public String getRemoteHost()
+    {
+        if (_socket==null)
+            return null;
+        if (_remote==null)
+            return null;
+        return _remote.getAddress().getCanonicalHostName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getRemotePort()
+     */
+    public int getRemotePort()
+    {
+        if (_socket==null)
+            return 0;
+        return _remote==null?-1:_remote.getPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.EndPoint#getConnection()
+     */
+    public Object getTransport()
+    {
+        return _channel;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flush()
+        throws IOException
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.bio.StreamEndPoint#setMaxIdleTime(int)
+     */
+    public void setMaxIdleTime(int timeMs) throws IOException
+    {
+        if (_socket!=null && timeMs!=_maxIdleTime)
+            _socket.setSoTimeout(timeMs>0?timeMs:0);
+        _maxIdleTime=timeMs;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java b/src/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
new file mode 100644
index 0000000..bf683bd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/DirectNIOBuffer.java
@@ -0,0 +1,354 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+
+import org.eclipse.jetty.io.AbstractBuffer;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * 
+ * 
+ */
+public class DirectNIOBuffer extends AbstractBuffer implements NIOBuffer
+{ 	
+    private static final Logger LOG = Log.getLogger(DirectNIOBuffer.class);
+
+    protected final ByteBuffer _buf;
+    private ReadableByteChannel _in;
+    private InputStream _inStream;
+    private WritableByteChannel _out;
+    private OutputStream _outStream;
+
+    public DirectNIOBuffer(int size)
+    {
+        super(READWRITE,NON_VOLATILE);
+        _buf = ByteBuffer.allocateDirect(size);
+        _buf.position(0);
+        _buf.limit(_buf.capacity());
+    }
+    
+    public DirectNIOBuffer(ByteBuffer buffer,boolean immutable)
+    {
+        super(immutable?IMMUTABLE:READWRITE,NON_VOLATILE);
+        if (!buffer.isDirect())
+            throw new IllegalArgumentException();
+        _buf = buffer;
+        setGetIndex(buffer.position());
+        setPutIndex(buffer.limit());
+    }
+
+    /**
+     * @param file
+     */
+    public DirectNIOBuffer(File file) throws IOException
+    {
+        super(READONLY,NON_VOLATILE);
+        FileInputStream fis = null;
+        FileChannel fc = null;
+        try
+        {
+            fis = new FileInputStream(file);
+            fc = fis.getChannel();
+            _buf = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+            setGetIndex(0);
+            setPutIndex((int)file.length());
+            _access=IMMUTABLE;
+        }
+        finally
+        {
+            if (fc != null) try {fc.close();} catch (IOException e){LOG.ignore(e);}
+            IO.close(fis);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDirect()
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] array()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int capacity()
+    {
+        return _buf.capacity();
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte peek(int position)
+    {
+        return _buf.get(position);
+    }
+
+    public int peek(int index, byte[] b, int offset, int length)
+    {
+        int l = length;
+        if (index+l > capacity())
+        {
+            l=capacity()-index;
+            if (l==0)
+                return -1;
+        }
+        
+        if (l < 0) 
+            return -1;
+        try
+        {
+            _buf.position(index);
+            _buf.get(b,offset,l);
+        }
+        finally
+        {
+            _buf.position(0);
+        }
+        
+        return l;
+    }
+
+    public void poke(int index, byte b)
+    {
+        if (isReadOnly()) throw new IllegalStateException(__READONLY);
+        if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
+        if (index > capacity())
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+        _buf.put(index,b);
+    }
+
+    @Override
+    public int poke(int index, Buffer src)
+    {
+        if (isReadOnly()) throw new IllegalStateException(__READONLY);
+
+        byte[] array=src.array();
+        if (array!=null)
+        {
+            return poke(index,array,src.getIndex(),src.length());
+        }
+        else
+        {
+            Buffer src_buf=src.buffer();
+            if (src_buf instanceof DirectNIOBuffer)
+            {
+                ByteBuffer src_bytebuf = ((DirectNIOBuffer)src_buf)._buf;
+                if (src_bytebuf==_buf)
+                    src_bytebuf=_buf.duplicate();
+                try
+                {   
+                    _buf.position(index);
+                    int space = _buf.remaining();
+                    
+                    int length=src.length();
+                    if (length>space)    
+                        length=space;
+                    
+                    src_bytebuf.position(src.getIndex());
+                    src_bytebuf.limit(src.getIndex()+length);
+                    
+                    _buf.put(src_bytebuf);
+                    return length;
+                }
+                finally
+                {
+                    _buf.position(0);
+                    src_bytebuf.limit(src_bytebuf.capacity());
+                    src_bytebuf.position(0);
+                }
+            }
+            else
+                return super.poke(index,src);
+        }
+    }
+    
+    @Override
+    public int poke(int index, byte[] b, int offset, int length)
+    {
+        if (isReadOnly()) throw new IllegalStateException(__READONLY);
+
+        if (index < 0) throw new IllegalArgumentException("index<0: " + index + "<0");
+
+        if (index + length > capacity())
+        {
+            length=capacity()-index;
+            if (length<0)
+                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
+        }
+
+        try
+        {
+            _buf.position(index);
+            
+            int space=_buf.remaining();
+            
+            if (length>space)
+                length=space;
+            if (length>0)
+                _buf.put(b,offset,length);
+            return length;
+        }
+        finally
+        {
+            _buf.position(0);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ByteBuffer getByteBuffer()
+    {
+        return _buf;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int readFrom(InputStream in, int max) throws IOException
+    {
+        if (_in==null || !_in.isOpen() || in!=_inStream)
+        {
+            _in=Channels.newChannel(in);
+            _inStream=in;
+        }
+
+        if (max<0 || max>space())
+            max=space();
+        int p = putIndex();
+        
+        try
+        {
+            int len=0, total=0, available=max;
+            int loop=0;
+            while (total<max) 
+            {
+                _buf.position(p);
+                _buf.limit(p+available);
+                len=_in.read(_buf);
+                if (len<0)
+                {
+                    _in=null;
+                    _inStream=in;
+                    break;
+                }
+                else if (len>0)
+                {
+                    p += len;
+                    total += len;
+                    available -= len;
+                    setPutIndex(p);
+                    loop=0;
+                }
+                else if (loop++>1)
+                    break;
+                if (in.available()<=0)
+                    break;
+            }
+            if (len<0 && total==0)
+                return -1;
+            return total;
+            
+        }
+        catch(IOException e)
+        {
+            _in=null;
+            _inStream=in;
+            throw e;
+        }
+        finally
+        {
+            if (_in!=null && !_in.isOpen())
+            {
+                _in=null;
+                _inStream=in;
+            }
+            _buf.position(0);
+            _buf.limit(_buf.capacity());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void writeTo(OutputStream out) throws IOException
+    {
+        if (_out==null || !_out.isOpen() || out!=_outStream)
+        {
+            _out=Channels.newChannel(out);
+            _outStream=out;
+        }
+
+        synchronized (_buf)
+        {
+            try
+            {
+                int loop=0;
+                while(hasContent() && _out.isOpen())
+                {
+                    _buf.position(getIndex());
+                    _buf.limit(putIndex());
+                    int len=_out.write(_buf);
+                    if (len<0)
+                        break;
+                    else if (len>0)
+                    {
+                        skip(len);
+                        loop=0;
+                    }
+                    else if (loop++>1)
+                        break;
+                }
+
+            }
+            catch(IOException e)
+            {
+                _out=null;
+                _outStream=null;
+                throw e;
+            }
+            finally
+            {
+                if (_out!=null && !_out.isOpen())
+                {
+                    _out=null;
+                    _outStream=null;
+                }
+                _buf.position(0);
+                _buf.limit(_buf.capacity());
+            }
+        }
+    }
+
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java b/src/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
new file mode 100644
index 0000000..44c2f46
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/IndirectNIOBuffer.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.ByteArrayBuffer;
+
+public class IndirectNIOBuffer extends ByteArrayBuffer implements NIOBuffer
+{
+    protected final ByteBuffer _buf;
+
+    /* ------------------------------------------------------------ */
+    public IndirectNIOBuffer(int size)
+    {
+        super(size,READWRITE,NON_VOLATILE);
+        _buf = ByteBuffer.wrap(_bytes);
+        _buf.position(0);
+        _buf.limit(_buf.capacity());
+    }
+
+    /* ------------------------------------------------------------ */
+    public IndirectNIOBuffer(ByteBuffer buffer,boolean immutable)
+    {
+        super(buffer.array(),0,0, immutable?IMMUTABLE:READWRITE,NON_VOLATILE);
+        if (buffer.isDirect())
+            throw new IllegalArgumentException();
+        _buf = buffer;
+        _get=buffer.position();
+        _put=buffer.limit();
+        buffer.position(0);
+        buffer.limit(buffer.capacity());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ByteBuffer getByteBuffer()
+    {
+        return _buf;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDirect()
+    {
+        return false;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/NIOBuffer.java b/src/java/org/eclipse/jetty/io/nio/NIOBuffer.java
new file mode 100644
index 0000000..4331971
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/NIOBuffer.java
@@ -0,0 +1,37 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.io.Buffer;
+
+/* ------------------------------------------------------------------------------- */
+/** 
+ * 
+ * 
+ */
+public interface NIOBuffer extends Buffer
+{
+    /* ------------------------------------------------------------ */
+    public ByteBuffer getByteBuffer();
+
+    /* ------------------------------------------------------------ */
+    public boolean isDirect();
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java b/src/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java
new file mode 100644
index 0000000..ce57b57
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/NetworkTrafficSelectChannelEndPoint.java
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class NetworkTrafficSelectChannelEndPoint extends SelectChannelEndPoint
+{
+    private static final Logger LOG = Log.getLogger(NetworkTrafficSelectChannelEndPoint.class);
+
+    private final List<NetworkTrafficListener> listeners;
+
+    public NetworkTrafficSelectChannelEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key, int maxIdleTime, List<NetworkTrafficListener> listeners) throws IOException
+    {
+        super(channel, selectSet, key, maxIdleTime);
+        this.listeners = listeners;
+    }
+
+    @Override
+    public int fill(Buffer buffer) throws IOException
+    {
+        int read = super.fill(buffer);
+        notifyIncoming(buffer, read);
+        return read;
+    }
+
+    @Override
+    public int flush(Buffer buffer) throws IOException
+    {
+        int position = buffer.getIndex();
+        int written = super.flush(buffer);
+        notifyOutgoing(buffer, position, written);
+        return written;
+    }
+
+    @Override
+    protected int gatheringFlush(Buffer header, ByteBuffer bbuf0, Buffer buffer, ByteBuffer bbuf1) throws IOException
+    {
+        int headerPosition = header.getIndex();
+        int headerLength = header.length();
+        int bufferPosition = buffer.getIndex();
+        int written = super.gatheringFlush(header, bbuf0, buffer,bbuf1);
+        notifyOutgoing(header, headerPosition, written > headerLength ? headerLength : written);
+        notifyOutgoing(buffer, bufferPosition, written > headerLength ? written - headerLength : 0);
+        return written;
+    }
+
+    public void notifyOpened()
+    {
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.opened(_socket);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyIncoming(Buffer buffer, int read)
+    {
+        if (listeners != null && !listeners.isEmpty() && read > 0)
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    Buffer view = buffer.asReadOnlyBuffer();
+                    listener.incoming(_socket, view);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyOutgoing(Buffer buffer, int position, int written)
+    {
+        if (listeners != null && !listeners.isEmpty() && written > 0)
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    Buffer view = buffer.asReadOnlyBuffer();
+                    view.setGetIndex(position);
+                    view.setPutIndex(position + written);
+                    listener.outgoing(_socket, view);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+
+    public void notifyClosed()
+    {
+        if (listeners != null && !listeners.isEmpty())
+        {
+            for (NetworkTrafficListener listener : listeners)
+            {
+                try
+                {
+                    listener.closed(_socket);
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java b/src/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java
new file mode 100644
index 0000000..6b8b2ef
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/RandomAccessFileBuffer.java
@@ -0,0 +1,196 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.WritableByteChannel;
+
+import org.eclipse.jetty.io.AbstractBuffer;
+import org.eclipse.jetty.io.Buffer;
+
+public class RandomAccessFileBuffer extends AbstractBuffer implements Buffer
+{
+    final RandomAccessFile _file;
+    final FileChannel _channel;
+    final int _capacity;
+
+    public RandomAccessFileBuffer(File file) 
+        throws FileNotFoundException
+    {
+        super(READWRITE,true);
+        assert file.length()<=Integer.MAX_VALUE;
+        _file = new RandomAccessFile(file,"rw");
+        _channel=_file.getChannel();
+        _capacity=Integer.MAX_VALUE;
+        setGetIndex(0);
+        setPutIndex((int)file.length());
+    }
+    
+    public RandomAccessFileBuffer(File file,int capacity) 
+        throws FileNotFoundException
+    {
+        super(READWRITE,true);
+        assert capacity>=file.length();
+        assert file.length()<=Integer.MAX_VALUE;
+        _capacity=capacity;
+        _file = new RandomAccessFile(file,"rw");
+        _channel=_file.getChannel();
+        setGetIndex(0);
+        setPutIndex((int)file.length());
+    }
+    
+    public RandomAccessFileBuffer(File file,int capacity,int access) 
+        throws FileNotFoundException
+    {
+        super(access,true);
+        assert capacity>=file.length();
+        assert file.length()<=Integer.MAX_VALUE;
+        _capacity=capacity;
+        _file = new RandomAccessFile(file,access==READWRITE?"rw":"r");
+        _channel=_file.getChannel();
+        setGetIndex(0);
+        setPutIndex((int)file.length());
+    }
+
+    public byte[] array()
+    {
+        return null;
+    }
+
+    public int capacity()
+    {
+        return _capacity;
+    }
+
+    @Override
+    public void clear()
+    {
+        try
+        {
+            synchronized (_file)
+            {
+                super.clear();
+                _file.setLength(0);
+            }
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    @Override
+    public byte peek()
+    {
+        synchronized (_file)
+        {
+            try
+            {
+                if (_get!=_file.getFilePointer())
+                    _file.seek(_get);
+                return _file.readByte();
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public byte peek(int index)
+    {
+        synchronized (_file)
+        {
+            try
+            {
+                _file.seek(index);
+                return _file.readByte();
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public int peek(int index, byte[] b, int offset, int length)
+    {
+        synchronized (_file)
+        {
+            try
+            {
+                _file.seek(index);
+                return _file.read(b,offset,length);
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public void poke(int index, byte b)
+    {
+        synchronized (_file)
+        {
+            try
+            {
+                _file.seek(index);
+                _file.writeByte(b);
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    public int poke(int index, byte[] b, int offset, int length)
+    {
+        synchronized (_file)
+        {
+            try
+            {
+                _file.seek(index);
+                _file.write(b,offset,length);
+                return length;
+            }
+            catch(Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+    
+    public int writeTo(WritableByteChannel channel,int index, int length)
+        throws IOException
+    {
+        synchronized (_file)
+        {
+            return (int)_channel.transferTo(index,length,channel);
+        }
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/src/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
new file mode 100644
index 0000000..9d16f5d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
@@ -0,0 +1,868 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Locale;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Endpoint that can be scheduled by {@link SelectorManager}.
+ */
+public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPoint, ConnectedEndPoint
+{
+    public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
+
+    private final boolean WORK_AROUND_JVM_BUG_6346658 = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("win");
+    private final SelectorManager.SelectSet _selectSet;
+    private final SelectorManager _manager;
+    private  SelectionKey _key;
+    private final Runnable _handler = new Runnable()
+        {
+            public void run() { handle(); }
+        };
+
+    /** The desired value for {@link SelectionKey#interestOps()} */
+    private int _interestOps;
+
+    /**
+     * The connection instance is the handler for any IO activity on the endpoint.
+     * There is a different type of connection for HTTP, AJP, WebSocket and
+     * ProxyConnect.   The connection may change for an SCEP as it is upgraded
+     * from HTTP to proxy connect or websocket.
+     */
+    private volatile AsyncConnection _connection;
+
+    private static final int STATE_NEEDS_DISPATCH=-1;
+    private static final int STATE_UNDISPATCHED=0;
+    private static final int STATE_DISPATCHED=1;
+    private static final int STATE_ASYNC=2;
+    private int _state;
+    
+    private boolean _onIdle;
+
+    /** true if the last write operation succeed and wrote all offered bytes */
+    private volatile boolean _writable = true;
+
+
+    /** True if a thread has is blocked in {@link #blockReadable(long)} */
+    private boolean _readBlocked;
+
+    /** True if a thread has is blocked in {@link #blockWritable(long)} */
+    private boolean _writeBlocked;
+
+    /** true if {@link SelectSet#destroyEndPoint(SelectChannelEndPoint)} has not been called */
+    private boolean _open;
+
+    private volatile long _idleTimestamp;
+    private volatile boolean _checkIdle;
+    
+    private boolean _interruptable;
+
+    private boolean _ishut;
+
+    /* ------------------------------------------------------------ */
+    public SelectChannelEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key, int maxIdleTime)
+        throws IOException
+    {
+        super(channel, maxIdleTime);
+
+        _manager = selectSet.getManager();
+        _selectSet = selectSet;
+        _state=STATE_UNDISPATCHED;
+        _onIdle=false;
+        _open=true;
+        _key = key;
+
+        setCheckForIdle(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public SelectionKey getSelectionKey()
+    {
+        synchronized (this)
+        {
+            return _key;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public SelectorManager getSelectManager()
+    {
+        return _manager;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setConnection(Connection connection)
+    {
+        Connection old=_connection;
+        _connection=(AsyncConnection)connection;
+        if (old!=null && old!=_connection)
+            _manager.endPointUpgraded(this,old);
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getIdleTimestamp()
+    {
+        return _idleTimestamp;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Called by selectSet to schedule handling
+     *
+     */
+    public void schedule()
+    {
+        synchronized (this)
+        {
+            // If there is no key, then do nothing
+            if (_key == null || !_key.isValid())
+            {
+                _readBlocked=false;
+                _writeBlocked=false;
+                this.notifyAll();
+                return;
+            }
+
+            // If there are threads dispatched reading and writing
+            if (_readBlocked || _writeBlocked)
+            {
+                // assert _dispatched;
+                if (_readBlocked && _key.isReadable())
+                    _readBlocked=false;
+                if (_writeBlocked && _key.isWritable())
+                    _writeBlocked=false;
+
+                // wake them up is as good as a dispatched.
+                this.notifyAll();
+
+                // we are not interested in further selecting
+                _key.interestOps(0);
+                if (_state<STATE_DISPATCHED)
+                    updateKey();
+                return;
+            }
+
+            // Remove writeable op
+            if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
+            {
+                // Remove writeable op
+                _interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE;
+                _key.interestOps(_interestOps);
+                _writable = true; // Once writable is in ops, only removed with dispatch.
+            }
+
+            // If dispatched, then deregister interest
+            if (_state>=STATE_DISPATCHED)
+                _key.interestOps(0);
+            else
+            {
+                // other wise do the dispatch
+                dispatch();
+                if (_state>=STATE_DISPATCHED && !_selectSet.getManager().isDeferringInterestedOps0())
+                {
+                    _key.interestOps(0);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void asyncDispatch()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case STATE_NEEDS_DISPATCH:
+                case STATE_UNDISPATCHED:
+                    dispatch();
+                    break;
+                    
+                case STATE_DISPATCHED:
+                case STATE_ASYNC:
+                    _state=STATE_ASYNC;
+                    break;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch()
+    {
+        synchronized(this)
+        {
+            if (_state<=STATE_UNDISPATCHED)
+            {
+                if (_onIdle)
+                    _state = STATE_NEEDS_DISPATCH;
+                else
+                {
+                    _state = STATE_DISPATCHED;
+                    boolean dispatched = _manager.dispatch(_handler);
+                    if(!dispatched)
+                    {
+                        _state = STATE_NEEDS_DISPATCH;
+                        LOG.warn("Dispatched Failed! "+this+" to "+_manager);
+                        updateKey();
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called when a dispatched thread is no longer handling the endpoint.
+     * The selection key operations are updated.
+     * @return If false is returned, the endpoint has been redispatched and
+     * thread must keep handling the endpoint.
+     */
+    protected boolean undispatch()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case STATE_ASYNC:
+                    _state=STATE_DISPATCHED;
+                    return false;
+
+                default:
+                    _state=STATE_UNDISPATCHED;
+                    updateKey();
+                    return true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void cancelTimeout(Task task)
+    {
+        getSelectSet().cancelTimeout(task);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void scheduleTimeout(Task task, long timeoutMs)
+    {
+        getSelectSet().scheduleTimeout(task,timeoutMs);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setCheckForIdle(boolean check)
+    {
+        if (check)
+        {
+            _idleTimestamp=System.currentTimeMillis();
+            _checkIdle=true;
+        }
+        else
+            _checkIdle=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCheckForIdle()
+    {
+        return _checkIdle;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void notIdle()
+    {
+        _idleTimestamp=System.currentTimeMillis();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void checkIdleTimestamp(long now)
+    {
+        if (isCheckForIdle() && _maxIdleTime>0)
+        {
+            final long idleForMs=now-_idleTimestamp;
+
+            if (idleForMs>_maxIdleTime)
+            {
+                // Don't idle out again until onIdleExpired task completes.
+                setCheckForIdle(false);
+                _manager.dispatch(new Runnable()
+                {
+                    public void run()
+                    {
+                        try
+                        {
+                            onIdleExpired(idleForMs);
+                        }
+                        finally
+                        {
+                            setCheckForIdle(true);
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onIdleExpired(long idleForMs)
+    {
+        try
+        {
+            synchronized (this)
+            {
+                _onIdle=true;
+            }
+
+            _connection.onIdleExpired(idleForMs);
+        }
+        finally
+        {
+            synchronized (this)
+            {
+                _onIdle=false;
+                if (_state==STATE_NEEDS_DISPATCH)
+                    dispatch();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int fill(Buffer buffer) throws IOException
+    {
+        int fill=super.fill(buffer);
+        if (fill>0)
+            notIdle();
+        return fill;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+    {
+        int l = super.flush(header, buffer, trailer);
+
+        // If there was something to write and it wasn't written, then we are not writable.
+        if (l==0 && ( header!=null && header.hasContent() || buffer!=null && buffer.hasContent() || trailer!=null && trailer.hasContent()))
+        {
+            synchronized (this)
+            {   
+                _writable=false;
+                if (_state<STATE_DISPATCHED)
+                    updateKey();
+            }
+        }
+        else if (l>0)
+        {
+            _writable=true;
+            notIdle();
+        }
+        return l;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    @Override
+    public int flush(Buffer buffer) throws IOException
+    {
+        int l = super.flush(buffer);
+
+        // If there was something to write and it wasn't written, then we are not writable.
+        if (l==0 && buffer!=null && buffer.hasContent())
+        {
+            synchronized (this)
+            {   
+                _writable=false;
+                if (_state<STATE_DISPATCHED)
+                    updateKey();
+            }
+        }
+        else if (l>0)
+        {
+            _writable=true;
+            notIdle();
+        }
+
+        return l;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Allows thread to block waiting for further events.
+     */
+    @Override
+    public boolean blockReadable(long timeoutMs) throws IOException
+    {
+        synchronized (this)
+        {
+            if (isInputShutdown())
+                throw new EofException();
+
+            long now=_selectSet.getNow();
+            long end=now+timeoutMs;
+            boolean check=isCheckForIdle();
+            setCheckForIdle(true);
+            try
+            {
+                _readBlocked=true;
+                while (!isInputShutdown() && _readBlocked)
+                {
+                    try
+                    {
+                        updateKey();
+                        this.wait(timeoutMs>0?(end-now):10000);
+                    }
+                    catch (final InterruptedException e)
+                    {
+                        LOG.warn(e);
+                        if (_interruptable)
+                            throw new InterruptedIOException(){{this.initCause(e);}};
+                    }
+                    finally
+                    {
+                        now=_selectSet.getNow();
+                    }
+
+                    if (_readBlocked && timeoutMs>0 && now>=end)
+                        return false;
+                }
+            }
+            finally
+            {
+                _readBlocked=false;
+                setCheckForIdle(check);
+            }
+        }
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Allows thread to block waiting for further events.
+     */
+    @Override
+    public boolean blockWritable(long timeoutMs) throws IOException
+    {
+        synchronized (this)
+        {
+            if (isOutputShutdown())
+                throw new EofException();
+
+            long now=_selectSet.getNow();
+            long end=now+timeoutMs;
+            boolean check=isCheckForIdle();
+            setCheckForIdle(true);
+            try
+            {
+                _writeBlocked=true;
+                while (_writeBlocked && !isOutputShutdown())
+                {
+                    try
+                    {
+                        updateKey();
+                        this.wait(timeoutMs>0?(end-now):10000);
+                    }
+                    catch (final InterruptedException e)
+                    {
+                        LOG.warn(e);
+                        if (_interruptable)
+                            throw new InterruptedIOException(){{this.initCause(e);}};
+                    }
+                    finally
+                    {
+                        now=_selectSet.getNow();
+                    }
+                    if (_writeBlocked && timeoutMs>0 && now>=end)
+                        return false;
+                }
+            }
+            finally
+            {
+                _writeBlocked=false;
+                setCheckForIdle(check);
+            }
+        }
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the interruptable mode of the endpoint.
+     * If set to false (default), then interrupts are assumed to be spurious 
+     * and blocking operations continue unless the endpoint has been closed.
+     * If true, then interrupts of blocking operations result in InterruptedIOExceptions
+     * being thrown.
+     * @param interupable
+     */
+    public void setInterruptable(boolean interupable)
+    {
+        synchronized (this)
+        {
+            _interruptable=interupable;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isInterruptable()
+    {
+        return _interruptable;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.AsyncEndPoint#scheduleWrite()
+     */
+    public void scheduleWrite()
+    {
+        if (_writable)
+            LOG.debug("Required scheduleWrite {}",this);
+
+        _writable=false;
+        updateKey();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWritable()
+    {
+        return _writable;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean hasProgressed()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Updates selection key. Adds operations types to the selection key as needed. No operations
+     * are removed as this is only done during dispatch. This method records the new key and
+     * schedules a call to doUpdateKey to do the keyChange
+     */
+    private void updateKey()
+    {
+        final boolean changed;
+        synchronized (this)
+        {
+            int current_ops=-1;
+            if (getChannel().isOpen())
+            {
+                boolean read_interest = _readBlocked || (_state<STATE_DISPATCHED && !_connection.isSuspended());
+                boolean write_interest= _writeBlocked || (_state<STATE_DISPATCHED && !_writable);
+
+                _interestOps =
+                    ((!_socket.isInputShutdown() && read_interest ) ? SelectionKey.OP_READ  : 0)
+                |   ((!_socket.isOutputShutdown()&& write_interest) ? SelectionKey.OP_WRITE : 0);
+                try
+                {
+                    current_ops = ((_key!=null && _key.isValid())?_key.interestOps():-1);
+                }
+                catch(Exception e)
+                {
+                    _key=null;
+                    LOG.ignore(e);
+                }
+            }
+            changed=_interestOps!=current_ops;
+        }
+
+        if(changed)
+        {
+            _selectSet.addChange(this);
+            _selectSet.wakeup();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Synchronize the interestOps with the actual key. Call is scheduled by a call to updateKey
+     */
+    void doUpdateKey()
+    {
+        synchronized (this)
+        {
+            if (getChannel().isOpen())
+            {
+                if (_interestOps>0)
+                {
+                    if (_key==null || !_key.isValid())
+                    {
+                        SelectableChannel sc = (SelectableChannel)getChannel();
+                        if (sc.isRegistered())
+                        {
+                            updateKey();
+                        }
+                        else
+                        {
+                            try
+                            {
+                                _key=((SelectableChannel)getChannel()).register(_selectSet.getSelector(),_interestOps,this);
+                            }
+                            catch (Exception e)
+                            {
+                                LOG.ignore(e);
+                                if (_key!=null && _key.isValid())
+                                {
+                                    _key.cancel();
+                                }
+
+                                if (_open)
+                                {
+                                    _selectSet.destroyEndPoint(this);
+                                }
+                                _open=false;
+                                _key = null;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        _key.interestOps(_interestOps);
+                    }
+                }
+                else
+                {
+                    if (_key!=null && _key.isValid())
+                        _key.interestOps(0);
+                    else
+                        _key=null;
+                }
+            }
+            else
+            {
+                if (_key!=null && _key.isValid())
+                    _key.cancel();
+
+                if (_open)
+                {
+                    _open=false;
+                    _selectSet.destroyEndPoint(this);
+                }
+                _key = null;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    protected void handle()
+    {
+        boolean dispatched=true;
+        try
+        {
+            while(dispatched)
+            {
+                try
+                {
+                    while(true)
+                    {
+                        final AsyncConnection next = (AsyncConnection)_connection.handle();
+                        if (next!=_connection)
+                        {
+                            LOG.debug("{} replaced {}",next,_connection);
+                            Connection old=_connection;
+                            _connection=next;
+                            _manager.endPointUpgraded(this,old);
+                            continue;
+                        }
+                        break;
+                    }
+                }
+                catch (ClosedChannelException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (EofException e)
+                {
+                    LOG.debug("EOF", e);
+                    try{close();}
+                    catch(IOException e2){LOG.ignore(e2);}
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e.toString());
+                    try{close();}
+                    catch(IOException e2){LOG.ignore(e2);}
+                }
+                catch (Throwable e)
+                {
+                    LOG.warn("handle failed", e);
+                    try{close();}
+                    catch(IOException e2){LOG.ignore(e2);}
+                }
+                finally
+                {
+                    if (!_ishut && isInputShutdown() && isOpen())
+                    {
+                        _ishut=true;
+                        try
+                        {
+                            _connection.onInputShutdown();
+                        }
+                        catch(Throwable x)
+                        {
+                            LOG.warn("onInputShutdown failed", x);
+                            try{close();}
+                            catch(IOException e2){LOG.ignore(e2);}
+                        }
+                        finally
+                        {
+                            updateKey();
+                        }
+                    }
+                    dispatched=!undispatch();
+                }
+            }
+        }
+        finally
+        {
+            if (dispatched)
+            {
+                dispatched=!undispatch();
+                while (dispatched)
+                {
+                    LOG.warn("SCEP.run() finally DISPATCHED");
+                    dispatched=!undispatch();
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.io.nio.ChannelEndPoint#close()
+     */
+    @Override
+    public void close() throws IOException
+    {
+        // On unix systems there is a JVM issue that if you cancel before closing, it can 
+        // cause the selector to block waiting for a channel to close and that channel can 
+        // block waiting for the remote end.  But on windows, if you don't cancel before a 
+        // close, then the selector can block anyway!
+        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=357318
+        if (WORK_AROUND_JVM_BUG_6346658)
+        {
+            try
+            {
+                SelectionKey key = _key;
+                if (key!=null)
+                    key.cancel();
+            }
+            catch (Throwable e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+        try
+        {
+            super.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+        finally
+        {
+            updateKey();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        SelectionKey key = _key;
+        String keyString = "";
+        if (key != null)
+        {
+            if (key.isValid())
+            {
+                if (key.isReadable())
+                    keyString += "r";
+                if (key.isWritable())
+                    keyString += "w";
+            }
+            else
+            {
+                keyString += "!";
+            }
+        }
+        else
+        {
+            keyString += "-";
+        }
+        return String.format("SCEP@%x{l(%s)<->r(%s),s=%d,open=%b,ishut=%b,oshut=%b,rb=%b,wb=%b,w=%b,i=%d%s}-{%s}",
+                hashCode(),
+                _socket.getRemoteSocketAddress(),
+                _socket.getLocalSocketAddress(),
+                _state,
+                isOpen(),
+                isInputShutdown(),
+                isOutputShutdown(),
+                _readBlocked,
+                _writeBlocked,
+                _writable,
+                _interestOps,
+                keyString,
+                _connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    public SelectSet getSelectSet()
+    {
+        return _selectSet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Don't set the SoTimeout
+     * @see org.eclipse.jetty.io.nio.ChannelEndPoint#setMaxIdleTime(int)
+     */
+    @Override
+    public void setMaxIdleTime(int timeMs) throws IOException
+    {
+        _maxIdleTime=timeMs;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/SelectorManager.java b/src/java/org/eclipse/jetty/io/nio/SelectorManager.java
new file mode 100644
index 0000000..538fbfa
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/SelectorManager.java
@@ -0,0 +1,1034 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.nio.channels.CancelledKeyException;
+import java.nio.channels.Channel;
+import java.nio.channels.ClosedSelectorException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The Selector Manager manages and number of SelectSets to allow
+ * NIO scheduling to scale to large numbers of connections.
+ * <p>
+ */
+public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
+{
+    public static final Logger LOG=Log.getLogger("org.eclipse.jetty.io.nio");
+
+    private static final int __MONITOR_PERIOD=Integer.getInteger("org.eclipse.jetty.io.nio.MONITOR_PERIOD",1000).intValue();
+    private static final int __MAX_SELECTS=Integer.getInteger("org.eclipse.jetty.io.nio.MAX_SELECTS",100000).intValue();
+    private static final int __BUSY_PAUSE=Integer.getInteger("org.eclipse.jetty.io.nio.BUSY_PAUSE",50).intValue();
+    private static final int __IDLE_TICK=Integer.getInteger("org.eclipse.jetty.io.nio.IDLE_TICK",400).intValue();
+
+    private int _maxIdleTime;
+    private int _lowResourcesMaxIdleTime;
+    private long _lowResourcesConnections;
+    private SelectSet[] _selectSet;
+    private int _selectSets=1;
+    private volatile int _set=0;
+    private boolean _deferringInterestedOps0=true;
+    private int _selectorPriorityDelta=0;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxIdleTime The maximum period in milli seconds that a connection may be idle before it is closed.
+     * @see #setLowResourcesMaxIdleTime(long)
+     */
+    public void setMaxIdleTime(long maxIdleTime)
+    {
+        _maxIdleTime=(int)maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param selectSets number of select sets to create
+     */
+    public void setSelectSets(int selectSets)
+    {
+        long lrc = _lowResourcesConnections * _selectSets;
+        _selectSets=selectSets;
+        _lowResourcesConnections=lrc/_selectSets;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the max idle time
+     */
+    public long getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the number of select sets in use
+     */
+    public int getSelectSets()
+    {
+        return _selectSets;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param i
+     * @return The select set
+     */
+    public SelectSet getSelectSet(int i)
+    {
+        return _selectSet[i];
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Register a channel
+     * @param channel
+     * @param att Attached Object
+     */
+    public void register(SocketChannel channel, Object att)
+    {
+        // The ++ increment here is not atomic, but it does not matter.
+        // so long as the value changes sometimes, then connections will
+        // be distributed over the available sets.
+
+        int s=_set++;
+        if (s<0)
+            s=-s;
+        s=s%_selectSets;
+        SelectSet[] sets=_selectSet;
+        if (sets!=null)
+        {
+            SelectSet set=sets[s];
+            set.addChange(channel,att);
+            set.wakeup();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Register a channel
+     * @param channel
+     */
+    public void register(SocketChannel channel)
+    {
+        // The ++ increment here is not atomic, but it does not matter.
+        // so long as the value changes sometimes, then connections will
+        // be distributed over the available sets.
+
+        int s=_set++;
+        if (s<0)
+            s=-s;
+        s=s%_selectSets;
+        SelectSet[] sets=_selectSet;
+        if (sets!=null)
+        {
+            SelectSet set=sets[s];
+            set.addChange(channel);
+            set.wakeup();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Register a {@link ServerSocketChannel}
+     * @param acceptChannel
+     */
+    public void register(ServerSocketChannel acceptChannel)
+    {
+        int s=_set++;
+        if (s<0)
+            s=-s;
+        s=s%_selectSets;
+        SelectSet set=_selectSet[s];
+        set.addChange(acceptChannel);
+        set.wakeup();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return delta The value to add to the selector thread priority.
+     */
+    public int getSelectorPriorityDelta()
+    {
+        return _selectorPriorityDelta;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the selector thread priorty delta.
+     * @param delta The value to add to the selector thread priority.
+     */
+    public void setSelectorPriorityDelta(int delta)
+    {
+        _selectorPriorityDelta=delta;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the lowResourcesConnections
+     */
+    public long getLowResourcesConnections()
+    {
+        return _lowResourcesConnections*_selectSets;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the number of connections, which if exceeded places this manager in low resources state.
+     * This is not an exact measure as the connection count is averaged over the select sets.
+     * @param lowResourcesConnections the number of connections
+     * @see #setLowResourcesMaxIdleTime(long)
+     */
+    public void setLowResourcesConnections(long lowResourcesConnections)
+    {
+        _lowResourcesConnections=(lowResourcesConnections+_selectSets-1)/_selectSets;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the lowResourcesMaxIdleTime
+     */
+    public long getLowResourcesMaxIdleTime()
+    {
+        return _lowResourcesMaxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when this SelectSet has more connections than {@link #getLowResourcesConnections()}
+     * @see #setMaxIdleTime(long)
+     */
+    public void setLowResourcesMaxIdleTime(long lowResourcesMaxIdleTime)
+    {
+        _lowResourcesMaxIdleTime=(int)lowResourcesMaxIdleTime;
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    public abstract boolean dispatch(Runnable task);
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see org.eclipse.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _selectSet = new SelectSet[_selectSets];
+        for (int i=0;i<_selectSet.length;i++)
+            _selectSet[i]= new SelectSet(i);
+
+        super.doStart();
+
+        // start a thread to Select
+        for (int i=0;i<getSelectSets();i++)
+        {
+            final int id=i;
+            boolean selecting=dispatch(new Runnable()
+            {
+                public void run()
+                {
+                    String name=Thread.currentThread().getName();
+                    int priority=Thread.currentThread().getPriority();
+                    try
+                    {
+                        SelectSet[] sets=_selectSet;
+                        if (sets==null)
+                            return;
+                        SelectSet set=sets[id];
+
+                        Thread.currentThread().setName(name+" Selector"+id);
+                        if (getSelectorPriorityDelta()!=0)
+                            Thread.currentThread().setPriority(Thread.currentThread().getPriority()+getSelectorPriorityDelta());
+                        LOG.debug("Starting {} on {}",Thread.currentThread(),this);
+                        while (isRunning())
+                        {
+                            try
+                            {
+                                set.doSelect();
+                            }
+                            catch(IOException e)
+                            {
+                                LOG.ignore(e);
+                            }
+                            catch(Exception e)
+                            {
+                                LOG.warn(e);
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        LOG.debug("Stopped {} on {}",Thread.currentThread(),this);
+                        Thread.currentThread().setName(name);
+                        if (getSelectorPriorityDelta()!=0)
+                            Thread.currentThread().setPriority(priority);
+                    }
+                }
+
+            });
+
+            if (!selecting)
+                throw new IllegalStateException("!Selecting");
+        }
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    protected void doStop() throws Exception
+    {
+        SelectSet[] sets= _selectSet;
+        _selectSet=null;
+        if (sets!=null)
+        {
+            for (SelectSet set : sets)
+            {
+                if (set!=null)
+                    set.stop();
+            }
+        }
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param endpoint
+     */
+    protected abstract void endPointClosed(SelectChannelEndPoint endpoint);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param endpoint
+     */
+    protected abstract void endPointOpened(SelectChannelEndPoint endpoint);
+
+    /* ------------------------------------------------------------ */
+    protected abstract void endPointUpgraded(ConnectedEndPoint endpoint,Connection oldConnection);
+
+    /* ------------------------------------------------------------------------------- */
+    public abstract AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new end point
+     * @param channel
+     * @param selectSet
+     * @param sKey the selection key
+     * @return the new endpoint {@link SelectChannelEndPoint}
+     * @throws IOException
+     */
+    protected abstract SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey sKey) throws IOException;
+
+    /* ------------------------------------------------------------------------------- */
+    protected void connectionFailed(SocketChannel channel,Throwable ex,Object attachment)
+    {
+        LOG.warn(ex+","+channel+","+attachment);
+        LOG.debug(ex);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        AggregateLifeCycle.dumpObject(out,this);
+        AggregateLifeCycle.dump(out,indent,TypeUtil.asList(_selectSet));
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    public class SelectSet implements Dumpable
+    {
+        private final int _setID;
+        private final Timeout _timeout;
+
+        private final ConcurrentLinkedQueue<Object> _changes = new ConcurrentLinkedQueue<Object>();
+
+        private volatile Selector _selector;
+
+        private volatile Thread _selecting;
+        private int _busySelects;
+        private long _monitorNext;
+        private boolean _pausing;
+        private boolean _paused;
+        private volatile long _idleTick;
+        private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>();
+
+        /* ------------------------------------------------------------ */
+        SelectSet(int acceptorID) throws Exception
+        {
+            _setID=acceptorID;
+
+            _idleTick = System.currentTimeMillis();
+            _timeout = new Timeout(this);
+            _timeout.setDuration(0L);
+
+            // create a selector;
+            _selector = Selector.open();
+            _monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void addChange(Object change)
+        {
+            _changes.add(change);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void addChange(SelectableChannel channel, Object att)
+        {
+            if (att==null)
+                addChange(channel);
+            else if (att instanceof EndPoint)
+                addChange(att);
+            else
+                addChange(new ChannelAndAttachment(channel,att));
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Select and dispatch tasks found from changes and the selector.
+         *
+         * @throws IOException
+         */
+        public void doSelect() throws IOException
+        {
+            try
+            {
+                _selecting=Thread.currentThread();
+                final Selector selector=_selector;
+                // Stopped concurrently ?
+                if (selector == null)
+                    return;
+
+                // Make any key changes required
+                Object change;
+                int changes=_changes.size();
+                while (changes-->0 && (change=_changes.poll())!=null)
+                {
+                    Channel ch=null;
+                    SelectionKey key=null;
+
+                    try
+                    {
+                        if (change instanceof EndPoint)
+                        {
+                            // Update the operations for a key.
+                            SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change;
+                            ch=endpoint.getChannel();
+                            endpoint.doUpdateKey();
+                        }
+                        else if (change instanceof ChannelAndAttachment)
+                        {
+                            // finish accepting/connecting this connection
+                            final ChannelAndAttachment asc = (ChannelAndAttachment)change;
+                            final SelectableChannel channel=asc._channel;
+                            ch=channel;
+                            final Object att = asc._attachment;
+
+                            if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected())
+                            {
+                                key = channel.register(selector,SelectionKey.OP_READ,att);
+                                SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key);
+                                key.attach(endpoint);
+                                endpoint.schedule();
+                            }
+                            else if (channel.isOpen())
+                            {
+                                key = channel.register(selector,SelectionKey.OP_CONNECT,att);
+                            }
+                        }
+                        else if (change instanceof SocketChannel)
+                        {
+                            // Newly registered channel
+                            final SocketChannel channel=(SocketChannel)change;
+                            ch=channel;
+                            key = channel.register(selector,SelectionKey.OP_READ,null);
+                            SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+                            key.attach(endpoint);
+                            endpoint.schedule();
+                        }
+                        else if (change instanceof ChangeTask)
+                        {
+                            ((Runnable)change).run();
+                        }
+                        else if (change instanceof Runnable)
+                        {
+                            dispatch((Runnable)change);
+                        }
+                        else
+                            throw new IllegalArgumentException(change.toString());
+                    }
+                    catch (CancelledKeyException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (Throwable e)
+                    {
+                        if (isRunning())
+                            LOG.warn(e);
+                        else
+                            LOG.debug(e);
+
+                        try
+                        {
+                            if (ch!=null)
+                                ch.close();
+                        }
+                        catch(IOException e2)
+                        {
+                            LOG.debug(e2);
+                        }
+                    }
+                }
+
+
+                // Do and instant select to see if any connections can be handled.
+                int selected=selector.selectNow();
+
+                long now=System.currentTimeMillis();
+
+                // if no immediate things to do
+                if (selected==0 && selector.selectedKeys().isEmpty())
+                {
+                    // If we are in pausing mode
+                    if (_pausing)
+                    {
+                        try
+                        {
+                            Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of  busy loop
+                        }
+                        catch(InterruptedException e)
+                        {
+                            LOG.ignore(e);
+                        }
+                        now=System.currentTimeMillis();
+                    }
+
+                    // workout how long to wait in select
+                    _timeout.setNow(now);
+                    long to_next_timeout=_timeout.getTimeToNext();
+
+                    long wait = _changes.size()==0?__IDLE_TICK:0L;
+                    if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout)
+                        wait = to_next_timeout;
+
+                    // If we should wait with a select
+                    if (wait>0)
+                    {
+                        long before=now;
+                        selector.select(wait);
+                        now = System.currentTimeMillis();
+                        _timeout.setNow(now);
+
+                        // If we are monitoring for busy selector
+                        // and this select did not wait more than 1ms
+                        if (__MONITOR_PERIOD>0 && now-before <=1)
+                        {
+                            // count this as a busy select and if there have been too many this monitor cycle
+                            if (++_busySelects>__MAX_SELECTS)
+                            {
+                                // Start injecting pauses
+                                _pausing=true;
+
+                                // if this is the first pause
+                                if (!_paused)
+                                {
+                                    // Log and dump some status
+                                    _paused=true;
+                                    LOG.warn("Selector {} is too busy, pausing!",this);
+                                }
+                            }
+                        }
+                    }
+                }
+
+                // have we been destroyed while sleeping
+                if (_selector==null || !selector.isOpen())
+                    return;
+
+                // Look for things to do
+                for (SelectionKey key: selector.selectedKeys())
+                {
+                    SocketChannel channel=null;
+
+                    try
+                    {
+                        if (!key.isValid())
+                        {
+                            key.cancel();
+                            SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment();
+                            if (endpoint != null)
+                                endpoint.doUpdateKey();
+                            continue;
+                        }
+
+                        Object att = key.attachment();
+                        if (att instanceof SelectChannelEndPoint)
+                        {
+                            if (key.isReadable()||key.isWritable())
+                                ((SelectChannelEndPoint)att).schedule();
+                        }
+                        else if (key.isConnectable())
+                        {
+                            // Complete a connection of a registered channel
+                            channel = (SocketChannel)key.channel();
+                            boolean connected=false;
+                            try
+                            {
+                                connected=channel.finishConnect();
+                            }
+                            catch(Exception e)
+                            {
+                                connectionFailed(channel,e,att);
+                            }
+                            finally
+                            {
+                                if (connected)
+                                {
+                                    key.interestOps(SelectionKey.OP_READ);
+                                    SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+                                    key.attach(endpoint);
+                                    endpoint.schedule();
+                                }
+                                else
+                                {
+                                    key.cancel();
+                                    channel.close();
+                                }
+                            }
+                        }
+                        else
+                        {
+                            // Wrap readable registered channel in an endpoint
+                            channel = (SocketChannel)key.channel();
+                            SelectChannelEndPoint endpoint = createEndPoint(channel,key);
+                            key.attach(endpoint);
+                            if (key.isReadable())
+                                endpoint.schedule();
+                        }
+                        key = null;
+                    }
+                    catch (CancelledKeyException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (Exception e)
+                    {
+                        if (isRunning())
+                            LOG.warn(e);
+                        else
+                            LOG.ignore(e);
+
+                        try
+                        {
+                            if (channel!=null)
+                                channel.close();
+                        }
+                        catch(IOException e2)
+                        {
+                            LOG.debug(e2);
+                        }
+
+                        if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid())
+                            key.cancel();
+                    }
+                }
+
+                // Everything always handled
+                selector.selectedKeys().clear();
+
+                now=System.currentTimeMillis();
+                _timeout.setNow(now);
+                Task task = _timeout.expired();
+                while (task!=null)
+                {
+                    if (task instanceof Runnable)
+                        dispatch((Runnable)task);
+                    task = _timeout.expired();
+                }
+
+                // Idle tick
+                if (now-_idleTick>__IDLE_TICK)
+                {
+                    _idleTick=now;
+
+                    final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections))
+                        ?(now+_maxIdleTime-_lowResourcesMaxIdleTime)
+                        :now;
+
+                    dispatch(new Runnable()
+                    {
+                        public void run()
+                        {
+                            for (SelectChannelEndPoint endp:_endPoints.keySet())
+                            {
+                                endp.checkIdleTimestamp(idle_now);
+                            }
+                        }
+                        public String toString() {return "Idle-"+super.toString();}
+                    });
+
+                }
+
+                // Reset busy select monitor counts
+                if (__MONITOR_PERIOD>0 && now>_monitorNext)
+                {
+                    _busySelects=0;
+                    _pausing=false;
+                    _monitorNext=now+__MONITOR_PERIOD;
+
+                }
+            }
+            catch (ClosedSelectorException e)
+            {
+                if (isRunning())
+                    LOG.warn(e);
+                else
+                    LOG.ignore(e);
+            }
+            catch (CancelledKeyException e)
+            {
+                LOG.ignore(e);
+            }
+            finally
+            {
+                _selecting=null;
+            }
+        }
+
+
+        /* ------------------------------------------------------------ */
+        private void renewSelector()
+        {
+            try
+            {
+                synchronized (this)
+                {
+                    Selector selector=_selector;
+                    if (selector==null)
+                        return;
+                    final Selector new_selector = Selector.open();
+                    for (SelectionKey k: selector.keys())
+                    {
+                        if (!k.isValid() || k.interestOps()==0)
+                            continue;
+
+                        final SelectableChannel channel = k.channel();
+                        final Object attachment = k.attachment();
+
+                        if (attachment==null)
+                            addChange(channel);
+                        else
+                            addChange(channel,attachment);
+                    }
+                    _selector.close();
+                    _selector=new_selector;
+                }
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException("recreating selector",e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public SelectorManager getManager()
+        {
+            return SelectorManager.this;
+        }
+
+        /* ------------------------------------------------------------ */
+        public long getNow()
+        {
+            return _timeout.getNow();
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @param task The task to timeout. If it implements Runnable, then
+         * expired will be called from a dispatched thread.
+         *
+         * @param timeoutMs
+         */
+        public void scheduleTimeout(Timeout.Task task, long timeoutMs)
+        {
+            if (!(task instanceof Runnable))
+                throw new IllegalArgumentException("!Runnable");
+            _timeout.schedule(task, timeoutMs);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void cancelTimeout(Timeout.Task task)
+        {
+            task.cancel();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void wakeup()
+        {
+            try
+            {
+                Selector selector = _selector;
+                if (selector!=null)
+                    selector.wakeup();
+            }
+            catch(Exception e)
+            {
+                addChange(new ChangeTask()
+                {
+                    public void run()
+                    {
+                        renewSelector();
+                    }
+                });
+
+                renewSelector();
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        private SelectChannelEndPoint createEndPoint(SocketChannel channel, SelectionKey sKey) throws IOException
+        {
+            SelectChannelEndPoint endp = newEndPoint(channel,this,sKey);
+            LOG.debug("created {}",endp);
+            endPointOpened(endp);
+            _endPoints.put(endp,this);
+            return endp;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void destroyEndPoint(SelectChannelEndPoint endp)
+        {
+            LOG.debug("destroyEndPoint {}",endp);
+            _endPoints.remove(endp);
+            endPointClosed(endp);
+        }
+
+        /* ------------------------------------------------------------ */
+        Selector getSelector()
+        {
+            return _selector;
+        }
+
+        /* ------------------------------------------------------------ */
+        void stop() throws Exception
+        {
+            // Spin for a while waiting for selector to complete
+            // to avoid unneccessary closed channel exceptions
+            try
+            {
+                for (int i=0;i<100 && _selecting!=null;i++)
+                {
+                    wakeup();
+                    Thread.sleep(10);
+                }
+            }
+            catch(Exception e)
+            {
+                LOG.ignore(e);
+            }
+
+            // close endpoints and selector
+            synchronized (this)
+            {
+                Selector selector=_selector;
+                for (SelectionKey key:selector.keys())
+                {
+                    if (key==null)
+                        continue;
+                    Object att=key.attachment();
+                    if (att instanceof EndPoint)
+                    {
+                        EndPoint endpoint = (EndPoint)att;
+                        try
+                        {
+                            endpoint.close();
+                        }
+                        catch(IOException e)
+                        {
+                            LOG.ignore(e);
+                        }
+                    }
+                }
+
+
+                _timeout.cancelAll();
+                try
+                {
+                    selector=_selector;
+                    if (selector != null)
+                        selector.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.ignore(e);
+                }
+                _selector=null;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public String dump()
+        {
+            return AggregateLifeCycle.dump(this);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void dump(Appendable out, String indent) throws IOException
+        {
+            out.append(String.valueOf(this)).append(" id=").append(String.valueOf(_setID)).append("\n");
+
+            Thread selecting = _selecting;
+
+            Object where = "not selecting";
+            StackTraceElement[] trace =selecting==null?null:selecting.getStackTrace();
+            if (trace!=null)
+            {
+                for (StackTraceElement t:trace)
+                    if (t.getClassName().startsWith("org.eclipse.jetty."))
+                    {
+                        where=t;
+                        break;
+                    }
+            }
+
+            Selector selector=_selector;
+            if (selector!=null)
+            {
+                final ArrayList<Object> dump = new ArrayList<Object>(selector.keys().size()*2);
+                dump.add(where);
+
+                final CountDownLatch latch = new CountDownLatch(1);
+
+                addChange(new ChangeTask()
+                {
+                    public void run()
+                    {
+                        dumpKeyState(dump);
+                        latch.countDown();
+                    }
+                });
+
+                try
+                {
+                    latch.await(5,TimeUnit.SECONDS);
+                }
+                catch(InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+
+                AggregateLifeCycle.dump(out,indent,dump);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public void dumpKeyState(List<Object> dumpto)
+        {
+            Selector selector=_selector;
+            Set<SelectionKey> keys = selector.keys();
+            dumpto.add(selector + " keys=" + keys.size());
+            for (SelectionKey key: keys)
+            {
+                if (key.isValid())
+                    dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps());
+                else
+                    dumpto.add(key.attachment()+" iOps=-1 rOps=-1");
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public String toString()
+        {
+            Selector selector=_selector;
+            return String.format("%s keys=%d selected=%d",
+                    super.toString(),
+                    selector != null && selector.isOpen() ? selector.keys().size() : -1,
+                    selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private static class ChannelAndAttachment
+    {
+        final SelectableChannel _channel;
+        final Object _attachment;
+
+        public ChannelAndAttachment(SelectableChannel channel, Object attachment)
+        {
+            super();
+            _channel = channel;
+            _attachment = attachment;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDeferringInterestedOps0()
+    {
+        return _deferringInterestedOps0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDeferringInterestedOps0(boolean deferringInterestedOps0)
+    {
+        _deferringInterestedOps0 = deferringInterestedOps0;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private interface ChangeTask extends Runnable
+    {}
+
+}
diff --git a/src/java/org/eclipse/jetty/io/nio/SslConnection.java b/src/java/org/eclipse/jetty/io/nio/SslConnection.java
new file mode 100644
index 0000000..3d27606
--- /dev/null
+++ b/src/java/org/eclipse/jetty/io/nio/SslConnection.java
@@ -0,0 +1,865 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.io.nio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout.Task;
+
+/* ------------------------------------------------------------ */
+/** SSL Connection.
+ * An AysyncConnection that acts as an interceptor between and EndPoint and another
+ * Connection, that implements TLS encryption using an {@link SSLEngine}.
+ * <p>
+ * The connector uses an {@link AsyncEndPoint} (like {@link SelectChannelEndPoint}) as
+ * it's source/sink of encrypted data.   It then provides {@link #getSslEndPoint()} to
+ * expose a source/sink of unencrypted data to another connection (eg HttpConnection).
+ */
+public class SslConnection extends AbstractConnection implements AsyncConnection
+{
+    private final Logger _logger = Log.getLogger("org.eclipse.jetty.io.nio.ssl");
+
+    private static final NIOBuffer __ZERO_BUFFER=new IndirectNIOBuffer(0);
+
+    private static final ThreadLocal<SslBuffers> __buffers = new ThreadLocal<SslBuffers>();
+    private final SSLEngine _engine;
+    private final SSLSession _session;
+    private AsyncConnection _connection;
+    private final SslEndPoint _sslEndPoint;
+    private int _allocations;
+    private SslBuffers _buffers;
+    private NIOBuffer _inbound;
+    private NIOBuffer _unwrapBuf;
+    private NIOBuffer _outbound;
+    private AsyncEndPoint _aEndp;
+    private boolean _allowRenegotiate=true;
+    private boolean _handshook;
+    private boolean _ishut;
+    private boolean _oshut;
+    private final AtomicBoolean _progressed = new AtomicBoolean();
+
+    /* ------------------------------------------------------------ */
+    /* this is a half baked buffer pool
+     */
+    private static class SslBuffers
+    {
+        final NIOBuffer _in;
+        final NIOBuffer _out;
+        final NIOBuffer _unwrap;
+
+        SslBuffers(int packetSize, int appSize)
+        {
+            _in=new IndirectNIOBuffer(packetSize);
+            _out=new IndirectNIOBuffer(packetSize);
+            _unwrap=new IndirectNIOBuffer(appSize);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public SslConnection(SSLEngine engine,EndPoint endp)
+    {
+        this(engine,endp,System.currentTimeMillis());
+    }
+
+    /* ------------------------------------------------------------ */
+    public SslConnection(SSLEngine engine,EndPoint endp, long timeStamp)
+    {
+        super(endp,timeStamp);
+        _engine=engine;
+        _session=_engine.getSession();
+        _aEndp=(AsyncEndPoint)endp;
+        _sslEndPoint = newSslEndPoint();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SslEndPoint newSslEndPoint()
+    {
+        return new SslEndPoint();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL re-negotiation is allowed (default false)
+     */
+    public boolean isAllowRenegotiate()
+    {
+        return _allowRenegotiate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
+     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
+     * does not have CVE-2009-3555 fixed, then re-negotiation should
+     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
+     * of renegotiates in u19 and with RFC5746 in u22.
+     *
+     * @param allowRenegotiate
+     *            true if re-negotiation is allowed (default false)
+     */
+    public void setAllowRenegotiate(boolean allowRenegotiate)
+    {
+        _allowRenegotiate = allowRenegotiate;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void allocateBuffers()
+    {
+        synchronized (this)
+        {
+            if (_allocations++==0)
+            {
+                if (_buffers==null)
+                {
+                    _buffers=__buffers.get();
+                    if (_buffers==null)
+                        _buffers=new SslBuffers(_session.getPacketBufferSize()*2,_session.getApplicationBufferSize()*2);
+                    _inbound=_buffers._in;
+                    _outbound=_buffers._out;
+                    _unwrapBuf=_buffers._unwrap;
+                    __buffers.set(null);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void releaseBuffers()
+    {
+        synchronized (this)
+        {
+            if (--_allocations==0)
+            {
+                if (_buffers!=null &&
+                    _inbound.length()==0 &&
+                    _outbound.length()==0 &&
+                    _unwrapBuf.length()==0)
+                {
+                    _inbound=null;
+                    _outbound=null;
+                    _unwrapBuf=null;
+                    __buffers.set(_buffers);
+                    _buffers=null;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection handle() throws IOException
+    {
+        try
+        {
+            allocateBuffers();
+
+            boolean progress=true;
+
+            while (progress)
+            {
+                progress=false;
+
+                // If we are handshook let the delegate connection
+                if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
+                    progress=process(null,null);
+
+                // handle the delegate connection
+                AsyncConnection next = (AsyncConnection)_connection.handle();
+                if (next!=_connection && next!=null)
+                {
+                    _connection=next;
+                    progress=true;
+                }
+
+                _logger.debug("{} handle {} progress={}", _session, this, progress);
+            }
+        }
+        finally
+        {
+            releaseBuffers();
+
+            if (!_ishut && _sslEndPoint.isInputShutdown() && _sslEndPoint.isOpen())
+            {
+                _ishut=true;
+                try
+                {
+                    _connection.onInputShutdown();
+                }
+                catch(Throwable x)
+                {
+                    _logger.warn("onInputShutdown failed", x);
+                    try{_sslEndPoint.close();}
+                    catch(IOException e2){
+                        _logger.ignore(e2);}
+                }
+            }
+        }
+
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        Connection connection = _sslEndPoint.getConnection();
+        if (connection != null && connection != this)
+            connection.onClose();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onIdleExpired(long idleForMs)
+    {
+        try
+        {
+            _logger.debug("onIdleExpired {}ms on {}",idleForMs,this);
+            if (_endp.isOutputShutdown())
+                _sslEndPoint.close();
+            else
+                _sslEndPoint.shutdownOutput();
+        }
+        catch (IOException e)
+        {
+            _logger.warn(e);
+            super.onIdleExpired(idleForMs);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onInputShutdown() throws IOException
+    {
+
+    }
+
+    /* ------------------------------------------------------------ */
+    private synchronized boolean process(Buffer toFill, Buffer toFlush) throws IOException
+    {
+        boolean some_progress=false;
+        try
+        {
+            // We need buffers to progress
+            allocateBuffers();
+
+            // if we don't have a buffer to put received data into
+            if (toFill==null)
+            {
+                // use the unwrapbuffer to hold received data.
+                _unwrapBuf.compact();
+                toFill=_unwrapBuf;
+            }
+            // Else if the fill buffer is too small for the SSL session
+            else if (toFill.capacity()<_session.getApplicationBufferSize())
+            {
+                // fill to the temporary unwrapBuffer
+                boolean progress=process(null,toFlush);
+
+                // if we received any data,
+                if (_unwrapBuf!=null && _unwrapBuf.hasContent())
+                {
+                    // transfer from temp buffer to fill buffer
+                    _unwrapBuf.skip(toFill.put(_unwrapBuf));
+                    return true;
+                }
+                else
+                    // return progress from recursive call
+                    return progress;
+            }
+            // Else if there is some temporary data
+            else if (_unwrapBuf!=null && _unwrapBuf.hasContent())
+            {
+                // transfer from temp buffer to fill buffer
+                _unwrapBuf.skip(toFill.put(_unwrapBuf));
+                return true;
+            }
+
+            // If we are here, we have a buffer ready into which we can put some read data.
+
+            // If we have no data to flush, flush the empty buffer
+            if (toFlush==null)
+                toFlush=__ZERO_BUFFER;
+
+            // While we are making progress processing SSL engine
+            boolean progress=true;
+            while (progress)
+            {
+                progress=false;
+
+                // Do any real IO
+                int filled=0,flushed=0;
+                try
+                {
+                    // Read any available data
+                    if (_inbound.space()>0 && (filled=_endp.fill(_inbound))>0)
+                        progress = true;
+
+                    // flush any output data
+                    if (_outbound.hasContent() && (flushed=_endp.flush(_outbound))>0)
+                        progress = true;
+                }
+                catch (IOException e)
+                {
+                    _endp.close();
+                    throw e;
+                }
+                finally
+                {
+                    _logger.debug("{} {} {} filled={}/{} flushed={}/{}",_session,this,_engine.getHandshakeStatus(),filled,_inbound.length(),flushed,_outbound.length());
+                }
+
+                // handle the current hand share status
+                switch(_engine.getHandshakeStatus())
+                {
+                    case FINISHED:
+                        throw new IllegalStateException();
+
+                    case NOT_HANDSHAKING:
+                    {
+                        // Try unwrapping some application data
+                        if (toFill.space()>0 && _inbound.hasContent() && unwrap(toFill))
+                            progress=true;
+
+                        // Try wrapping some application data
+                        if (toFlush.hasContent() && _outbound.space()>0 && wrap(toFlush))
+                            progress=true;
+                    }
+                    break;
+
+                    case NEED_TASK:
+                    {
+                        // A task needs to be run, so run it!
+                        Runnable task;
+                        while ((task=_engine.getDelegatedTask())!=null)
+                        {
+                            progress=true;
+                            task.run();
+                        }
+
+                    }
+                    break;
+
+                    case NEED_WRAP:
+                    {
+                        // The SSL needs to send some handshake data to the other side
+                        if (_handshook && !_allowRenegotiate)
+                            _endp.close();
+                        else if (wrap(toFlush))
+                            progress=true;
+                    }
+                    break;
+
+                    case NEED_UNWRAP:
+                    {
+                        // The SSL needs to receive some handshake data from the other side
+                        if (_handshook && !_allowRenegotiate)
+                            _endp.close();
+                        else if (!_inbound.hasContent()&&filled==-1)
+                        {
+                            // No more input coming
+                            _endp.shutdownInput();
+                        }
+                        else if (unwrap(toFill))
+                            progress=true;
+                    }
+                    break;
+                }
+
+                // pass on ishut/oshut state
+                if (_endp.isOpen() && _endp.isInputShutdown() && !_inbound.hasContent())
+                    closeInbound();
+
+                if (_endp.isOpen() && _engine.isOutboundDone() && !_outbound.hasContent())
+                    _endp.shutdownOutput();
+
+                // remember if any progress has been made
+                some_progress|=progress;
+            }
+
+            // If we are reading into the temp buffer and it has some content, then we should be dispatched.
+            if (toFill==_unwrapBuf && _unwrapBuf.hasContent() && !_connection.isSuspended())
+                _aEndp.dispatch();
+        }
+        finally
+        {
+            releaseBuffers();
+            if (some_progress)
+                _progressed.set(true);
+        }
+        return some_progress;
+    }
+
+    private void closeInbound()
+    {
+        try
+        {
+            _engine.closeInbound();
+        }
+        catch (SSLException x)
+        {
+            _logger.debug(x);
+        }
+    }
+
+    private synchronized boolean wrap(final Buffer buffer) throws IOException
+    {
+        ByteBuffer bbuf=extractByteBuffer(buffer);
+        final SSLEngineResult result;
+
+        synchronized(bbuf)
+        {
+            _outbound.compact();
+            ByteBuffer out_buffer=_outbound.getByteBuffer();
+            synchronized(out_buffer)
+            {
+                try
+                {
+                    bbuf.position(buffer.getIndex());
+                    bbuf.limit(buffer.putIndex());
+                    out_buffer.position(_outbound.putIndex());
+                    out_buffer.limit(out_buffer.capacity());
+                    result=_engine.wrap(bbuf,out_buffer);
+                    if (_logger.isDebugEnabled())
+                        _logger.debug("{} wrap {} {} consumed={} produced={}",
+                            _session,
+                            result.getStatus(),
+                            result.getHandshakeStatus(),
+                            result.bytesConsumed(),
+                            result.bytesProduced());
+
+
+                    buffer.skip(result.bytesConsumed());
+                    _outbound.setPutIndex(_outbound.putIndex()+result.bytesProduced());
+                }
+                catch(SSLException e)
+                {
+                    _logger.debug(String.valueOf(_endp), e);
+                    _endp.close();
+                    throw e;
+                }
+                finally
+                {
+                    out_buffer.position(0);
+                    out_buffer.limit(out_buffer.capacity());
+                    bbuf.position(0);
+                    bbuf.limit(bbuf.capacity());
+                }
+            }
+        }
+
+        switch(result.getStatus())
+        {
+            case BUFFER_UNDERFLOW:
+                throw new IllegalStateException();
+
+            case BUFFER_OVERFLOW:
+                break;
+
+            case OK:
+                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+                    _handshook=true;
+                break;
+
+            case CLOSED:
+                _logger.debug("wrap CLOSE {} {}",this,result);
+                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+                    _endp.close();
+                break;
+
+            default:
+                _logger.debug("{} wrap default {}",_session,result);
+            throw new IOException(result.toString());
+        }
+
+        return result.bytesConsumed()>0 || result.bytesProduced()>0;
+    }
+
+    private synchronized boolean unwrap(final Buffer buffer) throws IOException
+    {
+        if (!_inbound.hasContent())
+            return false;
+
+        ByteBuffer bbuf=extractByteBuffer(buffer);
+        final SSLEngineResult result;
+
+        synchronized(bbuf)
+        {
+            ByteBuffer in_buffer=_inbound.getByteBuffer();
+            synchronized(in_buffer)
+            {
+                try
+                {
+                    bbuf.position(buffer.putIndex());
+                    bbuf.limit(buffer.capacity());
+                    in_buffer.position(_inbound.getIndex());
+                    in_buffer.limit(_inbound.putIndex());
+
+                    result=_engine.unwrap(in_buffer,bbuf);
+                    if (_logger.isDebugEnabled())
+                        _logger.debug("{} unwrap {} {} consumed={} produced={}",
+                            _session,
+                            result.getStatus(),
+                            result.getHandshakeStatus(),
+                            result.bytesConsumed(),
+                            result.bytesProduced());
+
+                    _inbound.skip(result.bytesConsumed());
+                    _inbound.compact();
+                    buffer.setPutIndex(buffer.putIndex()+result.bytesProduced());
+                }
+                catch(SSLException e)
+                {
+                    _logger.debug(String.valueOf(_endp), e);
+                    _endp.close();
+                    throw e;
+                }
+                finally
+                {
+                    in_buffer.position(0);
+                    in_buffer.limit(in_buffer.capacity());
+                    bbuf.position(0);
+                    bbuf.limit(bbuf.capacity());
+                }
+            }
+        }
+
+        switch(result.getStatus())
+        {
+            case BUFFER_UNDERFLOW:
+                if (_endp.isInputShutdown())
+                    _inbound.clear();
+                break;
+
+            case BUFFER_OVERFLOW:
+                if (_logger.isDebugEnabled()) _logger.debug("{} unwrap {} {}->{}",_session,result.getStatus(),_inbound.toDetailString(),buffer.toDetailString());
+                break;
+
+            case OK:
+                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+                    _handshook=true;
+                break;
+
+            case CLOSED:
+                _logger.debug("unwrap CLOSE {} {}",this,result);
+                if (result.getHandshakeStatus()==HandshakeStatus.FINISHED)
+                    _endp.close();
+                break;
+
+            default:
+                _logger.debug("{} wrap default {}",_session,result);
+            throw new IOException(result.toString());
+        }
+
+        //if (LOG.isDebugEnabled() && result.bytesProduced()>0)
+        //    LOG.debug("{} unwrapped '{}'",_session,buffer);
+
+        return result.bytesConsumed()>0 || result.bytesProduced()>0;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private ByteBuffer extractByteBuffer(Buffer buffer)
+    {
+        if (buffer.buffer() instanceof NIOBuffer)
+            return ((NIOBuffer)buffer.buffer()).getByteBuffer();
+        return ByteBuffer.wrap(buffer.array());
+    }
+
+    /* ------------------------------------------------------------ */
+    public AsyncEndPoint getSslEndPoint()
+    {
+        return _sslEndPoint;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return String.format("%s %s", super.toString(), _sslEndPoint);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class SslEndPoint implements AsyncEndPoint
+    {
+        public SSLEngine getSslEngine()
+        {
+            return _engine;
+        }
+
+        public AsyncEndPoint getEndpoint()
+        {
+            return _aEndp;
+        }
+
+        public void shutdownOutput() throws IOException
+        {
+            synchronized (SslConnection.this)
+            {
+                _logger.debug("{} ssl endp.oshut {}",_session,this);
+                _engine.closeOutbound();
+                _oshut=true;
+            }
+            flush();
+        }
+
+        public boolean isOutputShutdown()
+        {
+            synchronized (SslConnection.this)
+            {
+                return _oshut||!isOpen()||_engine.isOutboundDone();
+            }
+        }
+
+        public void shutdownInput() throws IOException
+        {
+            _logger.debug("{} ssl endp.ishut!",_session);
+            // We do not do a closeInput here, as SSL does not support half close.
+            // isInputShutdown works it out itself from buffer state and underlying endpoint state.
+        }
+
+        public boolean isInputShutdown()
+        {
+            synchronized (SslConnection.this)
+            {
+                return _endp.isInputShutdown() &&
+                !(_unwrapBuf!=null&&_unwrapBuf.hasContent()) &&
+                !(_inbound!=null&&_inbound.hasContent());
+            }
+        }
+
+        public void close() throws IOException
+        {
+            _logger.debug("{} ssl endp.close",_session);
+            _endp.close();
+        }
+
+        public int fill(Buffer buffer) throws IOException
+        {
+            int size=buffer.length();
+            process(buffer, null);
+
+            int filled=buffer.length()-size;
+
+            if (filled==0 && isInputShutdown())
+                return -1;
+            return filled;
+        }
+
+        public int flush(Buffer buffer) throws IOException
+        {
+            int size = buffer.length();
+            process(null, buffer);
+            return size-buffer.length();
+        }
+
+        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+        {
+            if (header!=null && header.hasContent())
+                return flush(header);
+            if (buffer!=null && buffer.hasContent())
+                return flush(buffer);
+            if (trailer!=null && trailer.hasContent())
+                return flush(trailer);
+            return 0;
+        }
+
+        public boolean blockReadable(long millisecs) throws IOException
+        {
+            long now = System.currentTimeMillis();
+            long end=millisecs>0?(now+millisecs):Long.MAX_VALUE;
+
+            while (now<end)
+            {
+                if (process(null,null))
+                    break;
+                _endp.blockReadable(end-now);
+                now = System.currentTimeMillis();
+            }
+
+            return now<end;
+        }
+
+        public boolean blockWritable(long millisecs) throws IOException
+        {
+            return _endp.blockWritable(millisecs);
+        }
+
+        public boolean isOpen()
+        {
+            return _endp.isOpen();
+        }
+
+        public Object getTransport()
+        {
+            return _endp;
+        }
+
+        public void flush() throws IOException
+        {
+            process(null, null);
+        }
+
+        public void dispatch()
+        {
+            _aEndp.dispatch();
+        }
+
+        public void asyncDispatch()
+        {
+            _aEndp.asyncDispatch();
+        }
+
+        public void scheduleWrite()
+        {
+            _aEndp.scheduleWrite();
+        }
+
+        public void onIdleExpired(long idleForMs)
+        {
+            _aEndp.onIdleExpired(idleForMs);
+        }
+
+        public void setCheckForIdle(boolean check)
+        {
+            _aEndp.setCheckForIdle(check);
+        }
+
+        public boolean isCheckForIdle()
+        {
+            return _aEndp.isCheckForIdle();
+        }
+
+        public void scheduleTimeout(Task task, long timeoutMs)
+        {
+            _aEndp.scheduleTimeout(task,timeoutMs);
+        }
+
+        public void cancelTimeout(Task task)
+        {
+            _aEndp.cancelTimeout(task);
+        }
+
+        public boolean isWritable()
+        {
+            return _aEndp.isWritable();
+        }
+
+        public boolean hasProgressed()
+        {
+            return _progressed.getAndSet(false);
+        }
+
+        public String getLocalAddr()
+        {
+            return _aEndp.getLocalAddr();
+        }
+
+        public String getLocalHost()
+        {
+            return _aEndp.getLocalHost();
+        }
+
+        public int getLocalPort()
+        {
+            return _aEndp.getLocalPort();
+        }
+
+        public String getRemoteAddr()
+        {
+            return _aEndp.getRemoteAddr();
+        }
+
+        public String getRemoteHost()
+        {
+            return _aEndp.getRemoteHost();
+        }
+
+        public int getRemotePort()
+        {
+            return _aEndp.getRemotePort();
+        }
+
+        public boolean isBlocking()
+        {
+            return false;
+        }
+
+        public int getMaxIdleTime()
+        {
+            return _aEndp.getMaxIdleTime();
+        }
+
+        public void setMaxIdleTime(int timeMs) throws IOException
+        {
+            _aEndp.setMaxIdleTime(timeMs);
+        }
+
+        public Connection getConnection()
+        {
+            return _connection;
+        }
+
+        public void setConnection(Connection connection)
+        {
+            _connection=(AsyncConnection)connection;
+        }
+
+        public String toString()
+        {
+            // Do NOT use synchronized (SslConnection.this)
+            // because it's very easy to deadlock when debugging is enabled.
+            // We do a best effort to print the right toString() and that's it.
+            Buffer inbound = _inbound;
+            Buffer outbound = _outbound;
+            Buffer unwrap = _unwrapBuf;
+            int i = inbound == null? -1 : inbound.length();
+            int o = outbound == null ? -1 : outbound.length();
+            int u = unwrap == null ? -1 : unwrap.length();
+            return String.format("SSL %s i/o/u=%d/%d/%d ishut=%b oshut=%b {%s}",
+                    _engine.getHandshakeStatus(),
+                    i, o, u,
+                    _ishut, _oshut,
+                    _connection);
+        }
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/jmx/ConnectorServer.java b/src/java/org/eclipse/jetty/jmx/ConnectorServer.java
new file mode 100644
index 0000000..a00772a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/jmx/ConnectorServer.java
@@ -0,0 +1,195 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jmx;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.Map;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * AbstractLifeCycle wrapper for JMXConnector Server
+ */
+public class ConnectorServer extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(ConnectorServer.class);
+
+    JMXConnectorServer _connectorServer;
+    Registry _registry;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructs connector server
+     * 
+     * @param serviceURL the address of the new connector server.
+     * The actual address of the new connector server, as returned 
+     * by its getAddress method, will not necessarily be exactly the same.
+     * @param name object name string to be assigned to connector server bean
+     * @throws Exception
+     */
+    public ConnectorServer(JMXServiceURL serviceURL, String name)
+        throws Exception
+    {
+        this(serviceURL, null, name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructs connector server
+     * 
+     * @param svcUrl the address of the new connector server.
+     * The actual address of the new connector server, as returned 
+     * by its getAddress method, will not necessarily be exactly the same.
+     * @param environment  a set of attributes to control the new connector
+     * server's behavior. This parameter can be null. Keys in this map must
+     * be Strings. The appropriate type of each associated value depends on
+     * the attribute. The contents of environment are not changed by this call. 
+     * @param name object name string to be assigned to connector server bean
+     * @throws Exception
+     */
+    public ConnectorServer(JMXServiceURL svcUrl, Map<String,?> environment, String name)
+         throws Exception
+    {
+    	String urlPath = svcUrl.getURLPath();
+    	int idx = urlPath.indexOf("rmi://");
+    	if (idx > 0)
+    	{
+    	    String hostPort = urlPath.substring(idx+6, urlPath.indexOf('/', idx+6));
+    	    String regHostPort = startRegistry(hostPort);
+    	    if (regHostPort != null) {
+    	        urlPath = urlPath.replace(hostPort,regHostPort);
+    	        svcUrl = new JMXServiceURL(svcUrl.getProtocol(), svcUrl.getHost(), svcUrl.getPort(), urlPath);
+    	    }
+    	}
+        MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
+        _connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(svcUrl, environment, mbeanServer);
+        mbeanServer.registerMBean(_connectorServer,new ObjectName(name));
+    }
+
+	/* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        _connectorServer.start();
+        ShutdownThread.register(0, this);       
+        
+        LOG.info("JMX Remote URL: {}", _connectorServer.getAddress().toString());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        ShutdownThread.deregister(this);
+        _connectorServer.stop();
+        stopRegistry();
+    }
+
+    /**
+     * Check that local RMI registry is used, and ensure it is started. If local RMI registry is being used and not started, start it.
+     * 
+     * @param hostPath
+     *            hostname and port number of RMI registry
+     * @throws Exception
+     */
+    private String startRegistry(String hostPath) throws Exception
+    {
+        int rmiPort = 1099; // default RMI registry port
+        String rmiHost = hostPath;
+
+        int idx = hostPath.indexOf(':');
+        if (idx > 0)
+        {
+            rmiPort = Integer.parseInt(hostPath.substring(idx + 1));
+            rmiHost = hostPath.substring(0,idx);
+        }
+
+        // Verify that local registry is being used
+        InetAddress hostAddress = InetAddress.getByName(rmiHost);
+        if(hostAddress.isLoopbackAddress())
+        {
+            if (rmiPort == 0)
+            {
+                ServerSocket socket = new ServerSocket(0);
+                rmiPort = socket.getLocalPort();
+                socket.close();
+            }
+            else
+            {
+                try
+                {
+                    // Check if a local registry is already running
+                    LocateRegistry.getRegistry(rmiPort).list();
+                    return null;
+                }
+                catch (Exception ex)
+                {
+                    LOG.ignore(ex);
+                }
+            }
+
+            _registry = LocateRegistry.createRegistry(rmiPort);
+            Thread.sleep(1000);
+            
+            rmiHost = InetAddress.getLocalHost().getCanonicalHostName();
+            return rmiHost + ':' + Integer.toString(rmiPort);
+        }
+        
+        return null;
+    }
+
+    private void stopRegistry()
+    {
+        if (_registry != null)
+        {
+            try
+            {
+                UnicastRemoteObject.unexportObject(_registry,true);
+            }
+            catch (Exception ex)
+            {
+                LOG.ignore(ex);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/jmx/MBeanContainer.java b/src/java/org/eclipse/jetty/jmx/MBeanContainer.java
new file mode 100644
index 0000000..00a031f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/jmx/MBeanContainer.java
@@ -0,0 +1,366 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jmx;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.Container.Relationship;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.log.StdErrLog;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Container class for the MBean instances
+ */
+public class MBeanContainer extends AbstractLifeCycle implements Container.Listener, Dumpable
+{
+    private final static Logger LOG = Log.getLogger(MBeanContainer.class.getName());
+    private final static HashMap<String, Integer> __unique = new HashMap<String, Integer>();
+    
+    public final static void resetUnique()
+    {
+        synchronized (__unique)
+        {
+            __unique.clear();
+        }
+    }
+    
+    private final MBeanServer _server;
+    private final WeakHashMap<Object, ObjectName> _beans = new WeakHashMap<Object, ObjectName>();
+    private final WeakHashMap<ObjectName,List<Container.Relationship>> _relations = new WeakHashMap<ObjectName,List<Container.Relationship>>();
+    private String _domain = null;
+
+    /**
+     * Lookup an object name by instance
+     *
+     * @param object instance for which object name is looked up
+     * @return object name associated with specified instance, or null if not found
+     */
+    public synchronized ObjectName findMBean(Object object)
+    {
+        ObjectName bean = _beans.get(object);
+        return bean == null ? null : bean;
+    }
+
+    /**
+     * Lookup an instance by object name
+     *
+     * @param oname object name of instance
+     * @return instance associated with specified object name, or null if not found
+     */
+    public synchronized Object findBean(ObjectName oname)
+    {
+        for (Map.Entry<Object, ObjectName> entry : _beans.entrySet())
+        {
+            ObjectName bean = entry.getValue();
+            if (bean.equals(oname))
+                return entry.getKey();
+        }
+        return null;
+    }
+
+    /**
+     * Constructs MBeanContainer
+     *
+     * @param server instance of MBeanServer for use by container
+     */
+    public MBeanContainer(MBeanServer server)
+    {
+        _server = server;
+    }
+
+    /**
+     * Retrieve instance of MBeanServer used by container
+     *
+     * @return instance of MBeanServer
+     */
+    public MBeanServer getMBeanServer()
+    {
+        return _server;
+    }
+
+    /**
+     * Set domain to be used to add MBeans
+     *
+     * @param domain domain name
+     */
+    public void setDomain(String domain)
+    {
+        _domain = domain;
+    }
+
+    /**
+     * Retrieve domain name used to add MBeans
+     *
+     * @return domain name
+     */
+    public String getDomain()
+    {
+        return _domain;
+    }
+
+    /**
+     * Implementation of Container.Listener interface
+     *
+     * @see org.eclipse.jetty.util.component.Container.Listener#add(org.eclipse.jetty.util.component.Container.Relationship)
+     */
+    public synchronized void add(Relationship relationship)
+    {
+        LOG.debug("add {}",relationship);
+        ObjectName parent = _beans.get(relationship.getParent());
+        if (parent == null)
+        {
+            addBean(relationship.getParent());
+            parent = _beans.get(relationship.getParent());
+        }
+
+        ObjectName child = _beans.get(relationship.getChild());
+        if (child == null)
+        {
+            addBean(relationship.getChild());
+            child = _beans.get(relationship.getChild());
+        }
+
+        if (parent != null && child != null)
+        {
+            List<Container.Relationship> rels = _relations.get(parent);
+            if (rels==null)
+            {
+                rels=new ArrayList<Container.Relationship>();
+                _relations.put(parent,rels);
+            }
+            rels.add(relationship);
+        }
+    }
+
+    /**
+     * Implementation of Container.Listener interface
+     *
+     * @see org.eclipse.jetty.util.component.Container.Listener#remove(org.eclipse.jetty.util.component.Container.Relationship)
+     */
+    public synchronized void remove(Relationship relationship)
+    {
+        LOG.debug("remove {}",relationship);
+        ObjectName parent = _beans.get(relationship.getParent());
+        ObjectName child = _beans.get(relationship.getChild());
+
+        if (parent != null && child != null)
+        {
+            List<Container.Relationship> rels = _relations.get(parent);
+            if (rels!=null)
+            {
+                for (Iterator<Container.Relationship> i=rels.iterator();i.hasNext();)
+                {
+                    Container.Relationship r = i.next();
+                    if (relationship.equals(r) || r.getChild()==null)
+                        i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Implementation of Container.Listener interface
+     *
+     * @see org.eclipse.jetty.util.component.Container.Listener#removeBean(java.lang.Object)
+     */
+    public synchronized void removeBean(Object obj)
+    {
+        LOG.debug("removeBean {}",obj);
+        ObjectName bean = _beans.remove(obj);
+
+        if (bean != null)
+        {
+            List<Container.Relationship> beanRelations= _relations.remove(bean);
+            if (beanRelations != null)
+            {
+                LOG.debug("Unregister {}", beanRelations);
+                List<?> removeList = new ArrayList<Object>(beanRelations);
+                for (Object r : removeList)
+                {
+                    Container.Relationship relation = (Relationship)r;
+                    relation.getContainer().update(relation.getParent(), relation.getChild(), null, relation.getRelationship(), true);
+                }
+            }
+
+            try
+            {
+                _server.unregisterMBean(bean);
+                LOG.debug("Unregistered {}", bean);
+            }
+            catch (javax.management.InstanceNotFoundException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
+    /**
+     * Implementation of Container.Listener interface
+     *
+     * @see org.eclipse.jetty.util.component.Container.Listener#addBean(java.lang.Object)
+     */
+    public synchronized void addBean(Object obj)
+    {
+        LOG.debug("addBean {}",obj);
+        try
+        {
+            if (obj == null || _beans.containsKey(obj))
+                return;
+
+            Object mbean = ObjectMBean.mbeanFor(obj);
+            if (mbean == null)
+                return;
+
+            ObjectName oname = null;
+            if (mbean instanceof ObjectMBean)
+            {
+                ((ObjectMBean)mbean).setMBeanContainer(this);
+                oname = ((ObjectMBean)mbean).getObjectName();
+            }
+
+            //no override mbean object name, so make a generic one
+            if (oname == null)
+            {
+                String type = obj.getClass().getName().toLowerCase(Locale.ENGLISH);
+                int dot = type.lastIndexOf('.');
+                if (dot >= 0)
+                    type = type.substring(dot + 1);
+
+                String context = null;
+                if (mbean instanceof ObjectMBean)
+                {
+                    context = makeName(((ObjectMBean)mbean).getObjectContextBasis());
+                }
+
+                String name = null;
+                if (mbean instanceof ObjectMBean)
+                {
+                    name = makeName(((ObjectMBean)mbean).getObjectNameBasis());
+                }
+
+                StringBuffer buf = new StringBuffer();
+                buf.append("type=").append(type);
+                if (context != null && context.length()>1)
+                {
+                    buf.append(buf.length()>0 ? ",":"");
+                    buf.append("context=").append(context);
+                }
+                if (name != null && name.length()>1)
+                {
+                    buf.append(buf.length()>0 ? ",":"");
+                    buf.append("name=").append(name);
+                }
+                    
+                String basis = buf.toString();
+                Integer count;
+                synchronized (__unique)
+                {
+                    count = __unique.get(basis);
+                    count = count == null ? 0 : 1 + count;
+                    __unique.put(basis, count);
+                }
+
+                //if no explicit domain, create one
+                String domain = _domain;
+                if (domain == null)
+                    domain = obj.getClass().getPackage().getName();
+
+                oname = ObjectName.getInstance(domain + ":" + basis + ",id=" + count);
+            }
+
+            ObjectInstance oinstance = _server.registerMBean(mbean, oname);
+            LOG.debug("Registered {}", oinstance.getObjectName());
+            _beans.put(obj, oinstance.getObjectName());
+
+        }
+        catch (Exception e)
+        {
+            LOG.warn("bean: " + obj, e);
+        }
+    }
+
+    /**
+     * @param basis name to strip of special characters.
+     * @return normalized name
+     */
+    public String makeName(String basis)
+    {
+        if (basis==null)
+            return basis;
+        return basis.replace(':', '_').replace('*', '_').replace('?', '_').replace('=', '_').replace(',', '_').replace(' ', '_');
+    }
+
+    /**
+     * Perform actions needed to start lifecycle
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    public void doStart()
+    {
+        ShutdownThread.register(this);
+    }
+
+    /**
+     * Perform actions needed to stop lifecycle
+     *
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    public void doStop()
+    {
+        Set<Object> removeSet = new HashSet<Object>(_beans.keySet());
+        for (Object removeObj : removeSet)
+        {
+            removeBean(removeObj);
+        }
+    }
+
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        AggregateLifeCycle.dumpObject(out,this);
+        AggregateLifeCycle.dump(out, indent, _beans.entrySet());
+    }
+
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/jmx/ObjectMBean.java b/src/java/org/eclipse/jetty/jmx/ObjectMBean.java
new file mode 100644
index 0000000..c3d49ff
--- /dev/null
+++ b/src/java/org/eclipse/jetty/jmx/ObjectMBean.java
@@ -0,0 +1,777 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.jmx;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.AttributeNotFoundException;
+import javax.management.DynamicMBean;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.modelmbean.ModelMBean;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** ObjectMBean.
+ * A dynamic MBean that can wrap an arbitary Object instance.
+ * the attributes and methods exposed by this bean are controlled by
+ * the merge of property bundles discovered by names related to all
+ * superclasses and all superinterfaces.
+ *
+ * Attributes and methods exported may be "Object" and must exist on the
+ * wrapped object, or "MBean" and must exist on a subclass of OBjectMBean
+ * or "MObject" which exists on the wrapped object, but whose values are
+ * converted to MBean object names.
+ *
+ */
+public class ObjectMBean implements DynamicMBean
+{
+    private static final Logger LOG = Log.getLogger(ObjectMBean.class);
+
+    private static Class[] OBJ_ARG = new Class[]{Object.class};
+
+    protected Object _managed;
+    private MBeanInfo _info;
+    private Map _getters=new HashMap();
+    private Map _setters=new HashMap();
+    private Map _methods=new HashMap();
+    private Set _convert=new HashSet();
+    private ClassLoader _loader;
+    private MBeanContainer _mbeanContainer;
+
+    private static String OBJECT_NAME_CLASS = ObjectName.class.getName();
+    private static String OBJECT_NAME_ARRAY_CLASS = ObjectName[].class.getName();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create MBean for Object. Attempts to create an MBean for the object by searching the package
+     * and class name space. For example an object of the type
+     *
+     * <PRE>
+     * class com.acme.MyClass extends com.acme.util.BaseClass implements com.acme.Iface
+     * </PRE>
+     *
+     * Then this method would look for the following classes:
+     * <UL>
+     * <LI>com.acme.jmx.MyClassMBean
+     * <LI>com.acme.util.jmx.BaseClassMBean
+     * <LI>org.eclipse.jetty.jmx.ObjectMBean
+     * </UL>
+     *
+     * @param o The object
+     * @return A new instance of an MBean for the object or null.
+     */
+    public static Object mbeanFor(Object o)
+    {
+        try
+        {
+            Class oClass = o.getClass();
+            Object mbean = null;
+
+            while (mbean == null && oClass != null)
+            {
+                String pName = oClass.getPackage().getName();
+                String cName = oClass.getName().substring(pName.length() + 1);
+                String mName = pName + ".jmx." + cName + "MBean";
+                
+
+                try
+                {
+                    Class mClass = (Object.class.equals(oClass))?oClass=ObjectMBean.class:Loader.loadClass(oClass,mName,true);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("mbeanFor " + o + " mClass=" + mClass);
+
+                    try
+                    {
+                        Constructor constructor = mClass.getConstructor(OBJ_ARG);
+                        mbean=constructor.newInstance(new Object[]{o});
+                    }
+                    catch(Exception e)
+                    {
+                        LOG.ignore(e);
+                        if (ModelMBean.class.isAssignableFrom(mClass))
+                        {
+                            mbean=mClass.newInstance();
+                            ((ModelMBean)mbean).setManagedResource(o, "objectReference");
+                        }
+                    }
+
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("mbeanFor " + o + " is " + mbean);
+                    return mbean;
+                }
+                catch (ClassNotFoundException e)
+                {
+                    // The code below was modified to fix bugs 332200 and JETTY-1416 
+                    // The issue was caused by additional information added to the 
+                    // message after the class name when running in Apache Felix,
+                    // as well as before the class name when running in JBoss.
+                    if (e.getMessage().contains(mName))
+                        LOG.ignore(e);
+                    else
+                        LOG.warn(e);
+                }
+                catch (Error e)
+                {
+                    LOG.warn(e);
+                    mbean = null;
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                    mbean = null;
+                }
+
+                oClass = oClass.getSuperclass();
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return null;
+    }
+
+
+    public ObjectMBean(Object managedObject)
+    {
+        _managed = managedObject;
+        _loader = Thread.currentThread().getContextClassLoader();
+    }
+    
+    public Object getManagedObject()
+    {
+        return _managed;
+    }
+    
+    public ObjectName getObjectName()
+    {
+        return null;
+    }
+    
+    public String getObjectContextBasis()
+    {
+        return null;
+    }
+    
+    public String getObjectNameBasis()
+    {
+        return null;
+    }
+
+    protected void setMBeanContainer(MBeanContainer container)
+    {
+       this._mbeanContainer = container;
+    }
+
+    public MBeanContainer getMBeanContainer ()
+    {
+        return this._mbeanContainer;
+    }
+    
+    
+    public MBeanInfo getMBeanInfo()
+    {
+        try
+        {
+            if (_info==null)
+            {
+                // Start with blank lazy lists attributes etc.
+                String desc=null;
+                Object attributes=null;
+                Object constructors=null;
+                Object operations=null;
+                Object notifications=null;
+
+                // Find list of classes that can influence the mbean
+                Class o_class=_managed.getClass();
+                Object influences = findInfluences(null, _managed.getClass());
+
+                // Set to record defined items
+                Set defined=new HashSet();
+
+                // For each influence
+                for (int i=0;i<LazyList.size(influences);i++)
+                {
+                    Class oClass = (Class)LazyList.get(influences, i);
+
+                    // look for a bundle defining methods
+                    if (Object.class.equals(oClass))
+                        oClass=ObjectMBean.class;
+                    String pName = oClass.getPackage().getName();
+                    String cName = oClass.getName().substring(pName.length() + 1);
+                    String rName = pName.replace('.', '/') + "/jmx/" + cName+"-mbean";
+
+                    try
+                    {
+                        LOG.debug(rName);
+                        ResourceBundle bundle = Loader.getResourceBundle(o_class, rName,true,Locale.getDefault());
+
+                        
+                        // Extract meta data from bundle
+                        Enumeration e = bundle.getKeys();
+                        while (e.hasMoreElements())
+                        {
+                            String key = (String)e.nextElement();
+                            String value = bundle.getString(key);
+
+                            // Determin if key is for mbean , attribute or for operation
+                            if (key.equals(cName))
+                            {
+                                // set the mbean description
+                                if (desc==null)
+                                    desc=value;
+                            }
+                            else if (key.indexOf('(')>0)
+                            {
+                                // define an operation
+                                if (!defined.contains(key) && key.indexOf('[')<0)
+                                {
+                                    defined.add(key);
+                                    operations=LazyList.add(operations,defineOperation(key, value, bundle));
+                                }
+                            }
+                            else
+                            {
+                                // define an attribute
+                                if (!defined.contains(key))
+                                {
+                                    defined.add(key);
+                                    MBeanAttributeInfo info=defineAttribute(key, value);
+                                    if (info!=null)
+                                        attributes=LazyList.add(attributes,info);
+                                }
+                            }
+                        }
+                    }
+                    catch(MissingResourceException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                }
+
+                _info = new MBeanInfo(o_class.getName(),
+                                desc,
+                                (MBeanAttributeInfo[])LazyList.toArray(attributes, MBeanAttributeInfo.class),
+                                (MBeanConstructorInfo[])LazyList.toArray(constructors, MBeanConstructorInfo.class),
+                                (MBeanOperationInfo[])LazyList.toArray(operations, MBeanOperationInfo.class),
+                                (MBeanNotificationInfo[])LazyList.toArray(notifications, MBeanNotificationInfo.class));
+            }
+        }
+        catch(RuntimeException e)
+        {
+            LOG.warn(e);
+            throw e;
+        }
+        return _info;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException
+    {
+        Method getter = (Method) _getters.get(name);
+        if (getter == null)
+            throw new AttributeNotFoundException(name);
+        try
+        {
+            Object o = _managed;
+            if (getter.getDeclaringClass().isInstance(this))
+                o = this; // mbean method
+
+            // get the attribute
+            Object r=getter.invoke(o, (java.lang.Object[]) null);
+
+            // convert to ObjectName if need be.
+            if (r!=null && _convert.contains(name))
+            {
+                if (r.getClass().isArray())
+                {
+                    ObjectName[] on = new ObjectName[Array.getLength(r)];
+                    for (int i=0;i<on.length;i++)
+                        on[i]=_mbeanContainer.findMBean(Array.get(r, i));
+                    r=on;
+                }
+                else if (r instanceof Collection<?>)
+                {
+                    Collection<Object> c = (Collection<Object>)r;
+                    ObjectName[] on = new ObjectName[c.size()];
+                    int i=0;
+                    for (Object obj :c)
+                        on[i++]=_mbeanContainer.findMBean(obj);
+                    r=on;
+                }
+                else
+                {
+                    ObjectName mbean = _mbeanContainer.findMBean(r);
+                    if (mbean==null)
+                        return null;
+                    r=mbean;
+                }
+            }
+            return r;
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new AttributeNotFoundException(e.toString());
+        }
+        catch (InvocationTargetException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new ReflectionException(new Exception(e.getCause()));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public AttributeList getAttributes(String[] names)
+    {
+        AttributeList results = new AttributeList(names.length);
+        for (int i = 0; i < names.length; i++)
+        {
+            try
+            {
+                results.add(new Attribute(names[i], getAttribute(names[i])));
+            }
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION, e);
+            }
+        }
+        return results;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAttribute(Attribute attr) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
+    {
+        if (attr == null)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("setAttribute " + _managed + ":" +attr.getName() + "=" + attr.getValue());
+        Method setter = (Method) _setters.get(attr.getName());
+        if (setter == null)
+            throw new AttributeNotFoundException(attr.getName());
+        try
+        {
+            Object o = _managed;
+            if (setter.getDeclaringClass().isInstance(this))
+                o = this;
+
+            // get the value
+            Object value = attr.getValue();
+
+            // convert from ObjectName if need be
+            if (value!=null && _convert.contains(attr.getName()))
+            {
+                if (value.getClass().isArray())
+                {
+                    Class t=setter.getParameterTypes()[0].getComponentType();
+                    Object na = Array.newInstance(t,Array.getLength(value));
+                    for (int i=Array.getLength(value);i-->0;)
+                        Array.set(na, i, _mbeanContainer.findBean((ObjectName)Array.get(value, i)));
+                    value=na;
+                }
+                else
+                    value=_mbeanContainer.findBean((ObjectName)value);
+            }
+
+            // do the setting
+            setter.invoke(o, new Object[]{ value });
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new AttributeNotFoundException(e.toString());
+        }
+        catch (InvocationTargetException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new ReflectionException(new Exception(e.getCause()));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public AttributeList setAttributes(AttributeList attrs)
+    {
+        LOG.debug("setAttributes");
+
+        AttributeList results = new AttributeList(attrs.size());
+        Iterator iter = attrs.iterator();
+        while (iter.hasNext())
+        {
+            try
+            {
+                Attribute attr = (Attribute) iter.next();
+                setAttribute(attr);
+                results.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
+            }
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION, e);
+            }
+        }
+        return results;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object invoke(String name, Object[] params, String[] signature) throws MBeanException, ReflectionException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("invoke " + name);
+
+        String methodKey = name + "(";
+        if (signature != null)
+            for (int i = 0; i < signature.length; i++)
+                methodKey += (i > 0 ? "," : "") + signature[i];
+        methodKey += ")";
+
+        ClassLoader old_loader=Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(_loader);
+            Method method = (Method) _methods.get(methodKey);
+            if (method == null)
+                throw new NoSuchMethodException(methodKey);
+
+            Object o = _managed;
+            if (method.getDeclaringClass().isInstance(this))
+                o = this;
+            return method.invoke(o, params);
+        }
+        catch (NoSuchMethodException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new ReflectionException(e);
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new MBeanException(e);
+        }
+        catch (InvocationTargetException e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new ReflectionException(new Exception(e.getCause()));
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(old_loader);
+        }
+    }
+
+    private static Object findInfluences(Object influences, Class aClass)
+    {
+        if (aClass!=null)
+        {
+            // This class is an influence
+            influences=LazyList.add(influences,aClass);
+
+            // So are the super classes
+            influences=findInfluences(influences,aClass.getSuperclass());
+
+            // So are the interfaces
+            Class[] ifs = aClass.getInterfaces();
+            for (int i=0;ifs!=null && i<ifs.length;i++)
+                influences=findInfluences(influences,ifs[i]);
+        }
+        return influences;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Define an attribute on the managed object. The meta data is defined by looking for standard
+     * getter and setter methods. Descriptions are obtained with a call to findDescription with the
+     * attribute name.
+     *
+     * @param name
+     * @param metaData "description" or "access:description" or "type:access:description"  where type is
+     * one of: <ul>
+     * <li>"Object" The field/method is on the managed object.
+     * <li>"MBean" The field/method is on the mbean proxy object
+     * <li>"MObject" The field/method is on the managed object and value should be converted to MBean reference
+     * <li>"MMBean" The field/method is on the mbean proxy object and value should be converted to MBean reference
+     * </ul>
+     * the access is either "RW" or "RO".
+     */
+    public MBeanAttributeInfo defineAttribute(String name, String metaData)
+    {
+        String description = "";
+        boolean writable = true;
+        boolean onMBean = false;
+        boolean convert = false;
+
+        if (metaData!= null)
+        {
+            String[] tokens = metaData.split(":", 3);
+            for (int t=0;t<tokens.length-1;t++)
+            {
+                tokens[t]=tokens[t].trim();
+                if ("RO".equals(tokens[t]))
+                    writable=false;
+                else 
+                {
+                    onMBean=("MMBean".equalsIgnoreCase(tokens[t]) || "MBean".equalsIgnoreCase(tokens[t]));
+                    convert=("MMBean".equalsIgnoreCase(tokens[t]) || "MObject".equalsIgnoreCase(tokens[t]));
+                }
+            }
+            description=tokens[tokens.length-1];
+        }
+        
+
+        String uName = name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1);
+        Class oClass = onMBean ? this.getClass() : _managed.getClass();
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("defineAttribute "+name+" "+onMBean+":"+writable+":"+oClass+":"+description);
+
+        Class type = null;
+        Method getter = null;
+        Method setter = null;
+        Method[] methods = oClass.getMethods();
+        for (int m = 0; m < methods.length; m++)
+        {
+            if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
+                continue;
+
+            // Look for a getter
+            if (methods[m].getName().equals("get" + uName) && methods[m].getParameterTypes().length == 0)
+            {
+                if (getter != null)
+                {
+		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
+		    continue;
+		}
+                getter = methods[m];
+                if (type != null && !type.equals(methods[m].getReturnType()))
+                {
+		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
+		    continue;
+		}
+                type = methods[m].getReturnType();
+            }
+
+            // Look for an is getter
+            if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0)
+            {
+                if (getter != null)
+                {
+		    LOG.warn("Multiple mbean getters for attr " + name+ " in "+oClass);
+		    continue;
+		}
+                getter = methods[m];
+                if (type != null && !type.equals(methods[m].getReturnType()))
+                {
+		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
+		    continue;
+		}
+                type = methods[m].getReturnType();
+            }
+
+            // look for a setter
+            if (writable && methods[m].getName().equals("set" + uName) && methods[m].getParameterTypes().length == 1)
+            {
+                if (setter != null)
+                {
+		    LOG.warn("Multiple setters for mbean attr " + name+ " in "+oClass);
+		    continue;
+		}
+                setter = methods[m];
+                if (type != null && !type.equals(methods[m].getParameterTypes()[0]))
+                {
+		    LOG.warn("Type conflict for mbean attr " + name+ " in "+oClass);
+		    continue;
+		}
+                type = methods[m].getParameterTypes()[0];
+            }
+        }
+        
+        if (convert)
+        {
+            if (type==null)
+            {
+	        LOG.warn("No mbean type for " + name+" on "+_managed.getClass());
+		return null;
+	    }
+                
+            if (type.isPrimitive() && !type.isArray())
+            {
+	        LOG.warn("Cannot convert mbean primative " + name);
+		return null;
+	    }
+        }
+
+        if (getter == null && setter == null)
+        {
+	    LOG.warn("No mbean getter or setters found for " + name+ " in "+oClass);
+	    return null;
+	}
+
+        try
+        {
+            // Remember the methods
+            _getters.put(name, getter);
+            _setters.put(name, setter);
+
+            MBeanAttributeInfo info=null;
+            if (convert)
+            {
+                _convert.add(name);
+                if (type.isArray())
+                    info= new MBeanAttributeInfo(name,OBJECT_NAME_ARRAY_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
+
+                else
+                    info= new MBeanAttributeInfo(name,OBJECT_NAME_CLASS,description,getter!=null,setter!=null,getter!=null&&getter.getName().startsWith("is"));
+            }
+            else
+                info= new MBeanAttributeInfo(name,description,getter,setter);
+
+            return info;
+        }
+        catch (Exception e)
+        {
+            LOG.warn(name+": "+metaData, e);
+            throw new IllegalArgumentException(e.toString());
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Define an operation on the managed object. Defines an operation with parameters. Refection is
+     * used to determine find the method and it's return type. The description of the method is
+     * found with a call to findDescription on "name(signature)". The name and description of each
+     * parameter is found with a call to findDescription with "name(signature)[n]", the returned
+     * description is for the last parameter of the partial signature and is assumed to start with
+     * the parameter name, followed by a colon.
+     *
+     * @param metaData "description" or "impact:description" or "type:impact:description", type is
+     * the "Object","MBean", "MMBean" or "MObject" to indicate the method is on the object, the MBean or on the
+     * object but converted to an MBean reference, and impact is either "ACTION","INFO","ACTION_INFO" or "UNKNOWN".
+     */
+    private MBeanOperationInfo defineOperation(String signature, String metaData, ResourceBundle bundle)
+    {
+        String[] tokens=metaData.split(":",3);
+        int i=tokens.length-1;
+        String description=tokens[i--];
+        String impact_name = i<0?"UNKNOWN":tokens[i--].trim();
+        if (i==0)
+            tokens[0]=tokens[0].trim();
+        boolean onMBean= i==0 && ("MBean".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
+        boolean convert= i==0 && ("MObject".equalsIgnoreCase(tokens[0])||"MMBean".equalsIgnoreCase(tokens[0]));
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("defineOperation "+signature+" "+onMBean+":"+impact_name+":"+description);
+
+        Class oClass = onMBean ? this.getClass() : _managed.getClass();
+
+        try
+        {
+            // Resolve the impact
+            int impact=MBeanOperationInfo.UNKNOWN;
+            if (impact_name==null || impact_name.equals("UNKNOWN"))
+                impact=MBeanOperationInfo.UNKNOWN;
+            else if (impact_name.equals("ACTION"))
+                impact=MBeanOperationInfo.ACTION;
+            else if (impact_name.equals("INFO"))
+                impact=MBeanOperationInfo.INFO;
+            else if (impact_name.equals("ACTION_INFO"))
+                impact=MBeanOperationInfo.ACTION_INFO;
+            else
+                LOG.warn("Unknown impact '"+impact_name+"' for "+signature);
+
+
+            // split the signature
+            String[] parts=signature.split("[\\(\\)]");
+            String method_name=parts[0];
+            String arguments=parts.length==2?parts[1]:null;
+            String[] args=arguments==null?new String[0]:arguments.split(" *, *");
+
+            // Check types and normalize signature.
+            Class[] types = new Class[args.length];
+            MBeanParameterInfo[] pInfo = new MBeanParameterInfo[args.length];
+            signature=method_name;
+            for (i = 0; i < args.length; i++)
+            {
+                Class type = TypeUtil.fromName(args[i]);
+                if (type == null)
+                    type = Thread.currentThread().getContextClassLoader().loadClass(args[i]);
+                types[i] = type;
+                args[i] = type.isPrimitive() ? TypeUtil.toName(type) : args[i];
+                signature+=(i>0?",":"(")+args[i];
+            }
+            signature+=(i>0?")":"()");
+
+            // Build param infos
+            for (i = 0; i < args.length; i++)
+            {
+                String param_desc = bundle.getString(signature + "[" + i + "]");
+                parts=param_desc.split(" *: *",2);
+                if (LOG.isDebugEnabled())
+                    LOG.debug(parts[0]+": "+parts[1]);
+                pInfo[i] = new MBeanParameterInfo(parts[0].trim(), args[i], parts[1].trim());
+            }
+
+            // build the operation info
+            Method method = oClass.getMethod(method_name, types);
+            Class returnClass = method.getReturnType();
+            _methods.put(signature, method);
+            if (convert)
+                _convert.add(signature);
+
+            return new MBeanOperationInfo(method_name, description, pInfo, returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Operation '"+signature+"'", e);
+            throw new IllegalArgumentException(e.toString());
+        }
+
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/Authenticator.java b/src/java/org/eclipse/jetty/security/Authenticator.java
new file mode 100644
index 0000000..0b57353
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/Authenticator.java
@@ -0,0 +1,122 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Server;
+
+/**
+ * Authenticator Interface
+ * <p>
+ * An Authenticator is responsible for checking requests and sending
+ * response challenges in order to authenticate a request. 
+ * Various types of {@link Authentication} are returned in order to
+ * signal the next step in authentication.
+ * 
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public interface Authenticator
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Configure the Authenticator
+     * @param configuration
+     */
+    void setConfiguration(AuthConfiguration configuration);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The name of the authentication method
+     */
+    String getAuthMethod();
+    
+    /* ------------------------------------------------------------ */
+    /** Validate a response
+     * @param request The request
+     * @param response The response
+     * @param mandatory True if authentication is mandatory.
+     * @return An Authentication.  If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has 
+     * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will
+     * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}.  If Authentication is not manditory, then a 
+     * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned.
+     * 
+     * @throws ServerAuthException
+     */
+    Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request
+     * @param response
+     * @param mandatory
+     * @param validatedUser
+     * @return true if response is secure
+     * @throws ServerAuthException
+     */
+    boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException;
+    
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** 
+     * Authenticator Configuration
+     */
+    interface AuthConfiguration
+    {
+        String getAuthMethod();
+        String getRealmName();
+        
+        /** Get a SecurityHandler init parameter
+         * @see SecurityHandler#getInitParameter(String)
+         * @param param parameter name
+         * @return Parameter value or null
+         */
+        String getInitParameter(String param);
+        
+        /* ------------------------------------------------------------ */
+        /** Get a SecurityHandler init parameter names
+         * @see SecurityHandler#getInitParameterNames()
+         * @return Set of parameter names
+         */
+        Set<String> getInitParameterNames();
+        
+        LoginService getLoginService();
+        IdentityService getIdentityService();
+        boolean isSessionRenewedOnAuthentication();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** 
+     * Authenticator Factory
+     */
+    interface Factory
+    {
+        Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/ConstraintAware.java b/src/java/org/eclipse/jetty/security/ConstraintAware.java
new file mode 100644
index 0000000..e836125
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/ConstraintAware.java
@@ -0,0 +1,54 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface ConstraintAware
+{
+    List<ConstraintMapping> getConstraintMappings();
+    Set<String> getRoles();
+    
+    /* ------------------------------------------------------------ */
+    /** Set Constraint Mappings and roles.
+     * Can only be called during initialization.
+     * @param constraintMappings
+     * @param roles
+     */
+    void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles);
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Constraint Mapping.
+     * May be called for running webapplication as an annotated servlet is instantiated.
+     * @param mapping
+     */
+    void addConstraintMapping(ConstraintMapping mapping);
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add a Role definition.
+     * May be called on running webapplication as an annotated servlet is instantiated.
+     * @param role
+     */
+    void addRole(String role);
+}
diff --git a/src/java/org/eclipse/jetty/security/ConstraintMapping.java b/src/java/org/eclipse/jetty/security/ConstraintMapping.java
new file mode 100644
index 0000000..dd99c5b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/ConstraintMapping.java
@@ -0,0 +1,100 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.util.security.Constraint;
+
+public class ConstraintMapping
+{
+    String _method;
+    String[] _methodOmissions;
+
+    String _pathSpec;
+
+    Constraint _constraint;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraint.
+     */
+    public Constraint getConstraint()
+    {
+        return _constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint The constraint to set.
+     */
+    public void setConstraint(Constraint constraint)
+    {
+        this._constraint = constraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the method.
+     */
+    public String getMethod()
+    {
+        return _method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method The method to set.
+     */
+    public void setMethod(String method)
+    {
+        this._method = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    public String getPathSpec()
+    {
+        return _pathSpec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        this._pathSpec = pathSpec;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param omissions The http-method-omission
+     */
+    public void setMethodOmissions(String[] omissions)
+    {
+        _methodOmissions = omissions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String[] getMethodOmissions()
+    {
+        return _methodOmissions;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/src/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
new file mode 100644
index 0000000..2671871
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
@@ -0,0 +1,813 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.eclipse.jetty.http.HttpSchemes;
+import javax.servlet.HttpConstraintElement;
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.StringMap;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * Handler to enforce SecurityConstraints. This implementation is servlet spec
+ * 3.0 compliant and precomputes the constraint combinations for runtime
+ * efficiency.
+ *
+ */
+public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
+{
+    private static final String OMISSION_SUFFIX = ".omission";
+    
+    private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<ConstraintMapping>();
+    private final Set<String> _roles = new CopyOnWriteArraySet<String>();
+    private final PathMap _constraintMap = new PathMap();
+    private boolean _strict = true;
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
+    public static Constraint createConstraint()
+    {
+        return new Constraint();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param constraint
+     * @return
+     */
+    public static Constraint createConstraint(Constraint constraint)
+    {
+        try
+        {
+            return (Constraint)constraint.clone();
+        }
+        catch (CloneNotSupportedException e)
+        {
+            throw new IllegalStateException (e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a security constraint
+     * 
+     * @param name
+     * @param authenticate
+     * @param roles
+     * @param dataConstraint
+     * @return
+     */
+    public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
+    {
+        Constraint constraint = createConstraint();
+        if (name != null)
+            constraint.setName(name);
+        constraint.setAuthenticate(authenticate);
+        constraint.setRoles(roles);
+        constraint.setDataConstraint(dataConstraint);
+        return constraint;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param element
+     * @return
+     */
+    public static Constraint createConstraint (String name, HttpConstraintElement element)
+    {
+        return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());     
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param rolesAllowed
+     * @param permitOrDeny
+     * @param transport
+     * @return
+     */
+    public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
+    {
+        Constraint constraint = createConstraint();
+        
+        if (rolesAllowed == null || rolesAllowed.length==0)
+        {           
+            if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
+            {
+                //Equivalent to <auth-constraint> with no roles
+                constraint.setName(name+"-Deny");
+                constraint.setAuthenticate(true);
+            }
+            else
+            {
+                //Equivalent to no <auth-constraint>
+                constraint.setName(name+"-Permit");
+                constraint.setAuthenticate(false);
+            }
+        }
+        else
+        {
+            //Equivalent to <auth-constraint> with list of <security-role-name>s
+            constraint.setAuthenticate(true);
+            constraint.setRoles(rolesAllowed);
+            constraint.setName(name+"-RolesAllowed");           
+        } 
+
+        //Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
+        constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
+        return constraint; 
+    }
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec
+     * @param constraintMappings
+     * @return
+     */
+    public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            if (pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Take out of the constraint mappings those that match the 
+     * given path.
+     * 
+     * @param pathSpec
+     * @param constraintMappings a new list minus the matching constraints
+     * @return
+     */
+    public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
+    {
+        if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
+            return Collections.emptyList();
+        
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+        for (ConstraintMapping mapping:constraintMappings)
+        {
+            //Remove the matching mappings by only copying in non-matching mappings
+            if (!pathSpec.equals(mapping.getPathSpec()))
+            {
+               mappings.add(mapping);
+            }
+        }
+        return mappings;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
+     * 
+     * @param name
+     * @param pathSpec
+     * @param securityElement
+     * @return
+     */
+    public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
+    {
+        List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
+
+        //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
+        Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
+
+        //Create a mapping for the pathSpec for the default case
+        ConstraintMapping defaultMapping = new ConstraintMapping();
+        defaultMapping.setPathSpec(pathSpec);
+        defaultMapping.setConstraint(constraint);  
+        mappings.add(defaultMapping);
+
+
+        //See Spec 13.4.1.2 p127
+        List<String> methodOmissions = new ArrayList<String>();
+        
+        //make constraint mappings for this url for each of the HttpMethodConstraintElements
+        Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
+        if (methodConstraints != null)
+        {
+            for (HttpMethodConstraintElement methodConstraint:methodConstraints)
+            {
+                //Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
+                Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
+                ConstraintMapping mapping = new ConstraintMapping();
+                mapping.setConstraint(mconstraint);
+                mapping.setPathSpec(pathSpec);
+                if (methodConstraint.getMethodName() != null)
+                {
+                    mapping.setMethod(methodConstraint.getMethodName());
+                    //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+                    methodOmissions.add(methodConstraint.getMethodName());
+                }
+                mappings.add(mapping);
+            }
+        }
+        //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
+        if (methodOmissions.size() > 0)
+            defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
+
+        return mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the strict mode.
+     * @return true if the security handler is running in strict mode.
+     */
+    public boolean isStrict()
+    {
+        return _strict;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the strict mode of the security handler.
+     * <p>
+     * When in strict mode (the default), the full servlet specification
+     * will be implemented.
+     * If not in strict mode, some additional flexibility in configuration
+     * is allowed:<ul>
+     * <li>All users do not need to have a role defined in the deployment descriptor
+     * <li>The * role in a constraint applies to ANY role rather than all roles defined in
+     * the deployment descriptor.
+     * </ul>
+     *
+     * @param strict the strict to set
+     * @see #setRoles(Set)
+     * @see #setConstraintMappings(List, Set)
+     */
+    public void setStrict(boolean strict)
+    {
+        _strict = strict;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the constraintMappings.
+     */
+    public List<ConstraintMapping> getConstraintMappings()
+    {
+        return _constraintMappings;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set, from which the set of known roles
+     *            is determined.
+     */
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings)
+    {
+        setConstraintMappings(constraintMappings,null);
+    }
+
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set as array, from which the set of known roles
+     *            is determined.  Needed to retain API compatibility for 7.x
+     */
+    public void setConstraintMappings( ConstraintMapping[] constraintMappings )
+    {
+        setConstraintMappings( Arrays.asList(constraintMappings), null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Process the constraints following the combining rules in Servlet 3.0 EA
+     * spec section 13.7.1 Note that much of the logic is in the RoleInfo class.
+     *
+     * @param constraintMappings
+     *            The constraintMappings to set.
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
+    {
+        _constraintMappings.clear();
+        _constraintMappings.addAll(constraintMappings);
+
+        if (roles==null)
+        {
+            roles = new HashSet<String>();
+            for (ConstraintMapping cm : constraintMappings)
+            {
+                String[] cmr = cm.getConstraint().getRoles();
+                if (cmr!=null)
+                {
+                    for (String r : cmr)
+                        if (!"*".equals(r))
+                            roles.add(r);
+                }
+            }
+        }
+        setRoles(roles);
+        
+        if (isStarted())
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the known roles.
+     * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or
+     * {@link #setConstraintMappings(List, Set)}.
+     * @see #setStrict(boolean)
+     * @param roles The known roles (or null to determine them from the mappings)
+     */
+    public void setRoles(Set<String> roles)
+    {
+        _roles.clear();
+        _roles.addAll(roles);
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping)
+     */
+    public void addConstraintMapping(ConstraintMapping mapping)
+    {
+        _constraintMappings.add(mapping);
+        if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
+            for (String role :  mapping.getConstraint().getRoles())
+                addRole(role);
+
+        if (isStarted())
+        {
+            processConstraintMapping(mapping);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String)
+     */
+    public void addRole(String role)
+    {
+        boolean modified = _roles.add(role);
+        if (isStarted() && modified && _strict)
+        {
+            // Add the new role to currently defined any role role infos
+            for (Map<String,RoleInfo> map : (Collection<Map<String,RoleInfo>>)_constraintMap.values())
+            {
+                for (RoleInfo info : map.values())
+                {
+                    if (info.isAnyRole())
+                        info.addRole(role);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.SecurityHandler#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _constraintMap.clear();
+        if (_constraintMappings!=null)
+        {
+            for (ConstraintMapping mapping : _constraintMappings)
+            {
+                processConstraintMapping(mapping);
+            }
+        }
+        super.doStart();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _constraintMap.clear();
+        _constraintMappings.clear();
+        _roles.clear();
+        super.doStop();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create and combine the constraint with the existing processed
+     * constraints.
+     * 
+     * @param mapping
+     */
+    protected void processConstraintMapping(ConstraintMapping mapping)
+    {
+        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.get(mapping.getPathSpec());
+        if (mappings == null)
+        {
+            mappings = new StringMap();
+            _constraintMap.put(mapping.getPathSpec(),mappings);
+        }
+        RoleInfo allMethodsRoleInfo = mappings.get(null);
+        if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
+            return;
+       
+        if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
+        {
+           
+            processConstraintMappingWithMethodOmissions(mapping, mappings);
+            return;
+        }
+
+        String httpMethod = mapping.getMethod();       
+        RoleInfo roleInfo = mappings.get(httpMethod);
+        if (roleInfo == null)
+        {
+            roleInfo = new RoleInfo();
+            mappings.put(httpMethod,roleInfo);
+            if (allMethodsRoleInfo != null)
+            {
+                roleInfo.combine(allMethodsRoleInfo);
+            }
+        }
+        if (roleInfo.isForbidden())
+            return;
+
+        //add in info from the constraint
+        configureRoleInfo(roleInfo, mapping);
+        
+        if (roleInfo.isForbidden())
+        {
+            if (httpMethod == null)
+            {
+                mappings.clear();
+                mappings.put(null,roleInfo);
+            }
+        }
+        else
+        {
+            //combine with any entry that covers all methods
+            if (httpMethod == null)
+            {
+                for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
+                {
+                    if (entry.getKey() != null)
+                    {
+                        RoleInfo specific = entry.getValue();
+                        specific.combine(roleInfo);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Constraints that name method omissions are dealt with differently.
+     * We create an entry in the mappings with key "method.omission". This entry
+     * is only ever combined with other omissions for the same method to produce a
+     * consolidated RoleInfo. Then, when we wish to find the relevant constraints for
+     *  a given Request (in prepareConstraintInfo()), we consult 3 types of entries in 
+     * the mappings: an entry that names the method of the Request specifically, an
+     * entry that names constraints that apply to all methods, entries of the form
+     * method.omission, where the method of the Request is not named in the omission.
+     * @param mapping
+     * @param mappings
+     */
+    protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
+    {
+        String[] omissions = mapping.getMethodOmissions();
+
+        for (String omission:omissions)
+        {
+            //for each method omission, see if there is already a RoleInfo for it in mappings
+            RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
+            if (ri == null)
+            {
+                //if not, make one
+                ri = new RoleInfo();
+                mappings.put(omission+OMISSION_SUFFIX, ri);
+            }
+
+            //initialize RoleInfo or combine from ConstraintMapping
+            configureRoleInfo(ri, mapping);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize or update the RoleInfo from the constraint
+     * @param ri
+     * @param mapping
+     */
+    protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
+    {
+        Constraint constraint = mapping.getConstraint();
+        boolean forbidden = constraint.isForbidden();
+        ri.setForbidden(forbidden);
+        
+        //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
+        //which we need in order to do combining of omissions in prepareConstraintInfo
+        UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
+        ri.setUserDataConstraint(userDataConstraint);
+        
+
+        //if forbidden, no point setting up roles
+        if (!ri.isForbidden())
+        {
+            //add in the roles
+            boolean checked = mapping.getConstraint().getAuthenticate();
+            ri.setChecked(checked);
+            if (ri.isChecked())
+            {
+                if (mapping.getConstraint().isAnyRole())
+                {
+                    if (_strict)
+                    {
+                        // * means "all defined roles"
+                        for (String role : _roles)
+                            ri.addRole(role);
+                    }
+                    else
+                        // * means any role
+                        ri.setAnyRole(true);
+                }
+                else
+                {
+                    String[] newRoles = mapping.getConstraint().getRoles();
+                    for (String role : newRoles)
+                    {
+                        if (_strict &&!_roles.contains(role))
+                            throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
+                        ri.addRole(role);
+                    }
+                }
+            }
+        }
+    }
+   
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Find constraints that apply to the given path.
+     * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
+     * represents a merged set of user data constraints, roles etc -:
+     * <ol>
+     * <li>A mapping of an exact method name </li>
+     * <li>A mapping will null key that matches every method name</li>
+     * <li>Mappings with keys of the form "method.omission" that indicates it will match every method name EXCEPT that given</li>
+     * </ol>
+     * 
+     * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
+     */
+    protected Object prepareConstraintInfo(String pathInContext, Request request)
+    {
+        Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
+
+        if (mappings != null)
+        {
+            String httpMethod = request.getMethod();
+            RoleInfo roleInfo = mappings.get(httpMethod);
+            if (roleInfo == null)
+            {
+                //No specific http-method names matched
+                List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
+
+                //Get info for constraint that matches all methods if it exists
+                RoleInfo all = mappings.get(null);
+                if (all != null)
+                    applicableConstraints.add(all);
+          
+                
+                //Get info for constraints that name method omissions where target method name is not omitted
+                //(ie matches because target method is not omitted, hence considered covered by the constraint)
+                for (Entry<String, RoleInfo> entry: mappings.entrySet())
+                {
+                    if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
+                        applicableConstraints.add(entry.getValue());
+                }
+                
+                if (applicableConstraints.size() == 1)
+                    roleInfo = applicableConstraints.get(0);
+                else
+                {
+                    roleInfo = new RoleInfo();
+                    roleInfo.setUserDataConstraint(UserDataConstraint.None);
+                    
+                    for (RoleInfo r:applicableConstraints)
+                        roleInfo.combine(r);
+                }
+
+            }
+            return roleInfo;
+        }
+        return null;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.SecurityHandler#checkUserDataPermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object)
+     */
+    protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException
+    {
+        if (constraintInfo == null)
+            return true;
+
+        RoleInfo roleInfo = (RoleInfo)constraintInfo;
+        if (roleInfo.isForbidden())
+            return false;
+
+
+        UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint();
+        if (dataConstraint == null || dataConstraint == UserDataConstraint.None)
+        {
+            return true;
+        }
+        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
+        Connector connector = connection.getConnector();
+
+        if (dataConstraint == UserDataConstraint.Integral)
+        {
+            if (connector.isIntegral(request))
+                return true;
+            if (connector.getIntegralPort() > 0)
+            {
+                String scheme=connector.getIntegralScheme();
+                int port=connector.getIntegralPort();
+                String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
+                    ? "https://"+request.getServerName()+request.getRequestURI()
+                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();
+                if (request.getQueryString() != null)
+                    url += "?" + request.getQueryString();
+                response.setContentLength(0);
+                response.sendRedirect(url);
+            }
+            else
+                response.sendError(Response.SC_FORBIDDEN,"!Integral");
+
+            request.setHandled(true);
+            return false;
+        }
+        else if (dataConstraint == UserDataConstraint.Confidential)
+        {
+            if (connector.isConfidential(request))
+                return true;
+
+            if (connector.getConfidentialPort() > 0)
+            {
+                String scheme=connector.getConfidentialScheme();
+                int port=connector.getConfidentialPort();
+                String url = (HttpSchemes.HTTPS.equalsIgnoreCase(scheme) && port==443)
+                    ? "https://"+request.getServerName()+request.getRequestURI()
+                    : scheme + "://" + request.getServerName() + ":" + port + request.getRequestURI();                    
+                if (request.getQueryString() != null)
+                    url += "?" + request.getQueryString();
+                response.setContentLength(0);
+                response.sendRedirect(url);
+            }
+            else
+                response.sendError(Response.SC_FORBIDDEN,"!Confidential");
+
+            request.setHandled(true);
+            return false;
+        }
+        else
+        {
+            throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint);
+        }
+
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.SecurityHandler#isAuthMandatory(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object)
+     */
+    protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo)
+    {
+        if (constraintInfo == null)
+        {
+            return false;
+        }
+        return ((RoleInfo)constraintInfo).isChecked();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
+     */
+    @Override
+    protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
+            throws IOException
+    {
+        if (constraintInfo == null)
+        {
+            return true;
+        }
+        RoleInfo roleInfo = (RoleInfo)constraintInfo;
+
+        if (!roleInfo.isChecked())
+        {
+            return true;
+        }
+
+        if (roleInfo.isAnyRole() && request.getAuthType()!=null)
+            return true;
+
+        for (String role : roleInfo.getRoles())
+        {
+            if (userIdentity.isUserInRole(role, null))
+                return true;
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out,indent,
+                Collections.singleton(getLoginService()),
+                Collections.singleton(getIdentityService()),
+                Collections.singleton(getAuthenticator()),
+                Collections.singleton(_roles),
+                _constraintMap.entrySet(),
+                getBeans(),
+                TypeUtil.asList(getHandlers()));
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java b/src/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java
new file mode 100644
index 0000000..e2de9f7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/CrossContextPsuedoSession.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public interface CrossContextPsuedoSession<T>
+{
+
+    T fetch(HttpServletRequest request);
+
+    void store(T data, HttpServletResponse response);
+
+    void clear(HttpServletRequest request);
+
+}
diff --git a/src/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java b/src/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
new file mode 100644
index 0000000..534a6d4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java
@@ -0,0 +1,96 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.security.Authenticator.AuthConfiguration;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.ClientCertAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.SpnegoAuthenticator;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/**
+ * The Default Authenticator Factory.
+ * Uses the {@link AuthConfiguration#getAuthMethod()} to select an {@link Authenticator} from: <ul>
+ * <li>{@link org.eclipse.jetty.security.authentication.BasicAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.DigestAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.FormAuthenticator}</li>
+ * <li>{@link org.eclipse.jetty.security.authentication.ClientCertAuthenticator}</li>
+ * </ul>
+ * All authenticators derived from {@link org.eclipse.jetty.security.authentication.LoginAuthenticator} are 
+ * wrapped with a {@link org.eclipse.jetty.security.authentication.DeferredAuthentication}
+ * instance, which is used if authentication is not mandatory.
+ * 
+ * The Authentications from the {@link org.eclipse.jetty.security.authentication.FormAuthenticator} are always wrapped in a 
+ * {@link org.eclipse.jetty.security.authentication.SessionAuthentication}
+ * <p>
+ * If a {@link LoginService} has not been set on this factory, then
+ * the service is selected by searching the {@link Server#getBeans(Class)} results for
+ * a service that matches the realm name, else the first LoginService found is used.
+ *
+ */
+public class DefaultAuthenticatorFactory implements Authenticator.Factory
+{
+    LoginService _loginService;
+    
+    public Authenticator getAuthenticator(Server server, ServletContext context, AuthConfiguration configuration, IdentityService identityService, LoginService loginService)
+    {
+        String auth=configuration.getAuthMethod();
+        Authenticator authenticator=null;
+        
+        if (auth==null || Constraint.__BASIC_AUTH.equalsIgnoreCase(auth))
+            authenticator=new BasicAuthenticator();
+        else if (Constraint.__DIGEST_AUTH.equalsIgnoreCase(auth))
+            authenticator=new DigestAuthenticator();
+        else if (Constraint.__FORM_AUTH.equalsIgnoreCase(auth))
+            authenticator=new FormAuthenticator();
+        else if ( Constraint.__SPNEGO_AUTH.equalsIgnoreCase(auth) )
+            authenticator = new SpnegoAuthenticator();
+        else if ( Constraint.__NEGOTIATE_AUTH.equalsIgnoreCase(auth) ) // see Bug #377076
+            authenticator = new SpnegoAuthenticator(Constraint.__NEGOTIATE_AUTH);
+        if (Constraint.__CERT_AUTH.equalsIgnoreCase(auth)||Constraint.__CERT_AUTH2.equalsIgnoreCase(auth))
+            authenticator=new ClientCertAuthenticator();
+        
+        return authenticator;
+    }
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the loginService
+     */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        _loginService = loginService;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/DefaultIdentityService.java b/src/java/org/eclipse/jetty/security/DefaultIdentityService.java
new file mode 100644
index 0000000..7121faf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/DefaultIdentityService.java
@@ -0,0 +1,90 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Default Identity Service implementation.
+ * This service handles only role reference maps passed in an
+ * associated {@link org.eclipse.jetty.server.UserIdentity.Scope}.  If there are roles
+ * refs present, then associate will wrap the UserIdentity with one
+ * that uses the role references in the 
+ * {@link org.eclipse.jetty.server.UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+ * implementation. All other operations are effectively noops.
+ *
+ */
+public class DefaultIdentityService implements IdentityService
+{
+    /* ------------------------------------------------------------ */
+    public DefaultIdentityService()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * If there are roles refs present in the scope, then wrap the UserIdentity 
+     * with one that uses the role references in the {@link UserIdentity#isUserInRole(String, org.eclipse.jetty.server.UserIdentity.Scope)}
+     */
+    public Object associate(UserIdentity user)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void disassociate(Object previous) 
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object setRunAs(UserIdentity user, RunAsToken token)
+    {
+        return token;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void unsetRunAs(Object lastToken)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public RunAsToken newRunAsToken(String runAsName)
+    {
+        return new RoleRunAsToken(runAsName);
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getSystemUserIdentity()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity newUserIdentity(final Subject subject, final Principal userPrincipal, final String[] roles)
+    {
+        return new DefaultUserIdentity(subject,userPrincipal,roles);
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/security/DefaultUserIdentity.java b/src/java/org/eclipse/jetty/security/DefaultUserIdentity.java
new file mode 100644
index 0000000..7409887
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/DefaultUserIdentity.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * The default implementation of UserIdentity.
+ *
+ */
+public class DefaultUserIdentity implements UserIdentity
+{    
+    private final Subject _subject;
+    private final Principal _userPrincipal;
+    private final String[] _roles;
+    
+    public DefaultUserIdentity(Subject subject, Principal userPrincipal, String[] roles)
+    {
+        _subject=subject;
+        _userPrincipal=userPrincipal;
+        _roles=roles;
+    }
+
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _userPrincipal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        if (scope!=null && scope.getRoleRefMap()!=null)
+            role=scope.getRoleRefMap().get(role);
+
+        for (String r :_roles)
+            if (r.equals(role))
+                return true;
+        return false;
+    }
+
+    @Override
+    public String toString()
+    {
+        return DefaultUserIdentity.class.getSimpleName()+"('"+_userPrincipal+"')";
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java b/src/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
new file mode 100644
index 0000000..0b016a5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/HashCrossContextPsuedoSession.java
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @version $Rev: 4660 $ $Date: 2009-02-25 17:29:53 +0100 (Wed, 25 Feb 2009) $
+ */
+public class HashCrossContextPsuedoSession<T> implements CrossContextPsuedoSession<T>
+{
+    private final String _cookieName;
+
+    private final String _cookiePath;
+
+    private final Random _random = new SecureRandom();
+
+    private final Map<String, T> _data = new HashMap<String, T>();
+
+    public HashCrossContextPsuedoSession(String cookieName, String cookiePath)
+    {
+        this._cookieName = cookieName;
+        this._cookiePath = cookiePath == null ? "/" : cookiePath;
+    }
+
+    public T fetch(HttpServletRequest request)
+    {
+        for (Cookie cookie : request.getCookies())
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                return _data.get(key);
+            }
+        }
+        return null;
+    }
+
+    public void store(T datum, HttpServletResponse response)
+    {
+        String key;
+
+        synchronized (_data)
+        {
+            // Create new ID
+            while (true)
+            {
+                key = Long.toString(Math.abs(_random.nextLong()), 30 + (int) (System.currentTimeMillis() % 7));
+                if (!_data.containsKey(key)) break;
+            }
+
+            _data.put(key, datum);
+        }
+
+        Cookie cookie = new Cookie(_cookieName, key);
+        cookie.setPath(_cookiePath);
+        response.addCookie(cookie);
+    }
+
+    public void clear(HttpServletRequest request)
+    {
+        for (Cookie cookie : request.getCookies())
+        {
+            if (_cookieName.equals(cookie.getName()))
+            {
+                String key = cookie.getValue();
+                _data.remove(key);
+                break;
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/HashLoginService.java b/src/java/org/eclipse/jetty/security/HashLoginService.java
new file mode 100644
index 0000000..55f7ed2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/HashLoginService.java
@@ -0,0 +1,180 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.security.PropertyUserStore.UserListener;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * Properties User Realm.
+ * 
+ * An implementation of UserRealm that stores users and roles in-memory in HashMaps.
+ * <P>
+ * Typically these maps are populated by calling the load() method or passing a properties resource to the constructor. The format of the properties file is:
+ * 
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ * 
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ * 
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class HashLoginService extends MappedLoginService implements UserListener
+{
+    private static final Logger LOG = Log.getLogger(HashLoginService.class);
+
+    private PropertyUserStore _propertyUserStore;
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name)
+    {
+        setName(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashLoginService(String name, String config)
+    {
+        setName(name);
+        setConfig(config);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void getConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Resource getConfigResource()
+    {
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load realm users from properties file. The property file maps usernames to password specs followed by an optional comma separated list of role names.
+     * 
+     * @param config
+     *            Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void loadUsers() throws IOException
+    {
+        // TODO: Consider refactoring MappedLoginService to not have to override with unused methods
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        
+        if (_propertyUserStore == null)
+        {
+            if(LOG.isDebugEnabled())
+                LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " refreshInterval: " + _refreshInterval);
+            
+            _propertyUserStore = new PropertyUserStore();
+            _propertyUserStore.setRefreshInterval(_refreshInterval);
+            _propertyUserStore.setConfig(_config);
+            _propertyUserStore.registerUserListener(this);
+            _propertyUserStore.start();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void update(String userName, Credential credential, String[] roleArray)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("update: " + userName + " Roles: " + roleArray.length);
+        putUser(userName,credential,roleArray);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void remove(String userName)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("remove: " + userName);
+        removeUser(userName);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/IdentityService.java b/src/java/org/eclipse/jetty/security/IdentityService.java
new file mode 100644
index 0000000..3cb8cb5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/IdentityService.java
@@ -0,0 +1,95 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+
+/* ------------------------------------------------------------ */
+/**
+ * Associates UserIdentities from with threads and UserIdentity.Contexts.
+ * 
+ */
+public interface IdentityService
+{
+    final static String[] NO_ROLES = new String[]{}; 
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a user identity with the current thread.
+     * This is called with as a thread enters the 
+     * {@link SecurityHandler#handle(String, Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+     * method and then again with a null argument as that call exits.
+     * @param user The current user or null for no user to associated.
+     * @return an object representing the previous associated state
+     */
+    Object associate(UserIdentity user);
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Disassociate the user identity from the current thread 
+     * and restore previous identity.
+     * @param previous The opaque object returned from a call to {@link IdentityService#associate(UserIdentity)}
+     */
+    void disassociate(Object previous);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Associate a runas Token with the current user and thread.
+     * @param user The UserIdentity
+     * @param token The runAsToken to associate.
+     * @return The previous runAsToken or null.
+     */
+    Object setRunAs(UserIdentity user, RunAsToken token);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Disassociate the current runAsToken from the thread
+     * and reassociate the previous token.
+     * @param token RUNAS returned from previous associateRunAs call
+     */
+    void unsetRunAs(Object token);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new UserIdentity for use with this identity service.
+     * The UserIdentity should be immutable and able to be cached.
+     * 
+     * @param subject Subject to include in UserIdentity
+     * @param userPrincipal Principal to include in UserIdentity.  This will be returned from getUserPrincipal calls
+     * @param roles set of roles to include in UserIdentity.
+     * @return A new immutable UserIdententity
+     */
+    UserIdentity newUserIdentity(Subject subject, Principal userPrincipal, String[] roles);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new RunAsToken from a runAsName (normally a role).
+     * @param runAsName Normally a role name
+     * @return A new immutable RunAsToken
+     */
+    RunAsToken newRunAsToken(String runAsName);
+
+    /* ------------------------------------------------------------ */
+    UserIdentity getSystemUserIdentity();
+}
diff --git a/src/java/org/eclipse/jetty/security/JDBCLoginService.java b/src/java/org/eclipse/jetty/security/JDBCLoginService.java
new file mode 100644
index 0000000..8fd9edf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/JDBCLoginService.java
@@ -0,0 +1,283 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashMapped User Realm with JDBC as data source. JDBCLoginService extends
+ * HashULoginService and adds a method to fetch user information from database.
+ * The login() method checks the inherited Map for the user. If the user is not
+ * found, it will fetch details from the database and populate the inherited
+ * Map. It then calls the superclass login() method to perform the actual
+ * authentication. Periodically (controlled by configuration parameter),
+ * internal hashes are cleared. Caching can be disabled by setting cache refresh
+ * interval to zero. Uses one database connection that is initialized at
+ * startup. Reconnect on failures. authenticate() is 'synchronized'.
+ * 
+ * An example properties file for configuration is in
+ * $JETTY_HOME/etc/jdbcRealm.properties
+ * 
+ * @version $Id: JDBCLoginService.java 4792 2009-03-18 21:55:52Z gregw $
+ * 
+ * 
+ * 
+ * 
+ */
+
+public class JDBCLoginService extends MappedLoginService
+{
+    private static final Logger LOG = Log.getLogger(JDBCLoginService.class);
+
+    private String _config;
+    private String _jdbcDriver;
+    private String _url;
+    private String _userName;
+    private String _password;
+    private String _userTableKey;
+    private String _userTablePasswordField;
+    private String _roleTableRoleField;
+    private int _cacheTime;
+    private long _lastHashPurge;
+    private Connection _con;
+    private String _userSql;
+    private String _roleSql;
+
+
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService()
+        throws IOException
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name)
+        throws IOException
+    {
+        setName(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, String config)
+        throws IOException
+    {
+        setName(name);
+        setConfig(config);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public JDBCLoginService(String name, IdentityService identityService, String config)
+        throws IOException
+    {
+        setName(name);
+        setIdentityService(identityService);
+        setConfig(config);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.MappedLoginService#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        properties.load(resource.getInputStream());
+
+        _jdbcDriver = properties.getProperty("jdbcdriver");
+        _url = properties.getProperty("url");
+        _userName = properties.getProperty("username");
+        _password = properties.getProperty("password");
+        String _userTable = properties.getProperty("usertable");
+        _userTableKey = properties.getProperty("usertablekey");
+        String _userTableUserField = properties.getProperty("usertableuserfield");
+        _userTablePasswordField = properties.getProperty("usertablepasswordfield");
+        String _roleTable = properties.getProperty("roletable");
+        String _roleTableKey = properties.getProperty("roletablekey");
+        _roleTableRoleField = properties.getProperty("roletablerolefield");
+        String _userRoleTable = properties.getProperty("userroletable");
+        String _userRoleTableUserKey = properties.getProperty("userroletableuserkey");
+        String _userRoleTableRoleKey = properties.getProperty("userroletablerolekey");
+        _cacheTime = new Integer(properties.getProperty("cachetime"));
+
+        if (_jdbcDriver == null || _jdbcDriver.equals("")
+            || _url == null
+            || _url.equals("")
+            || _userName == null
+            || _userName.equals("")
+            || _password == null
+            || _cacheTime < 0)
+        {
+            LOG.warn("UserRealm " + getName() + " has not been properly configured");
+        }
+        _cacheTime *= 1000;
+        _lastHashPurge = 0;
+        _userSql = "select " + _userTableKey + "," + _userTablePasswordField + " from " + _userTable + " where " + _userTableUserField + " = ?";
+        _roleSql = "select r." + _roleTableRoleField
+                   + " from "
+                   + _roleTable
+                   + " r, "
+                   + _userRoleTable
+                   + " u where u."
+                   + _userRoleTableUserKey
+                   + " = ?"
+                   + " and r."
+                   + _roleTableKey
+                   + " = u."
+                   + _userRoleTableRoleKey;
+        
+        Loader.loadClass(this.getClass(), _jdbcDriver).newInstance();
+        super.doStart();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Load JDBC connection configuration from properties file.
+     * 
+     * @param config Filename or url of user properties file.
+     */
+    public void setConfig(String config)
+    {        
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _config=config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * (re)Connect to database with parameters setup by loadConfig()
+     */
+    public void connectDatabase()
+    {
+        try
+        {
+            Class.forName(_jdbcDriver);
+            _con = DriverManager.getConnection(_url, _userName, _password);
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not connect to database; will try later", e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object credentials)
+    {
+        long now = System.currentTimeMillis();
+        if (now - _lastHashPurge > _cacheTime || _cacheTime == 0)
+        {
+            _users.clear();
+            _lastHashPurge = now;
+            closeConnection();
+        }
+        
+        return super.login(username,credentials);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void loadUsers()
+    {   
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected UserIdentity loadUser(String username)
+    {
+        try
+        {
+            if (null == _con) 
+                connectDatabase();
+
+            if (null == _con) 
+                throw new SQLException("Can't connect to database");
+
+            PreparedStatement stat = _con.prepareStatement(_userSql);
+            stat.setObject(1, username);
+            ResultSet rs = stat.executeQuery();
+
+            if (rs.next())
+            {
+                int key = rs.getInt(_userTableKey);
+                String credentials = rs.getString(_userTablePasswordField);
+                stat.close();
+
+                stat = _con.prepareStatement(_roleSql);
+                stat.setInt(1, key);
+                rs = stat.executeQuery();
+                List<String> roles = new ArrayList<String>();
+                while (rs.next())
+                    roles.add(rs.getString(_roleTableRoleField));
+
+                stat.close();
+                return putUser(username, Credential.getCredential(credentials),roles.toArray(new String[roles.size()]));
+            }
+        }
+        catch (SQLException e)
+        {
+            LOG.warn("UserRealm " + getName() + " could not load user information from database", e);
+            closeConnection();
+        }
+        return null;
+    }
+
+    /**
+     * Close an existing connection
+     */
+    private void closeConnection ()
+    {
+        if (_con != null)
+        {
+            if (LOG.isDebugEnabled()) LOG.debug("Closing db connection for JDBCUserRealm");
+            try { _con.close(); }catch (Exception e) {LOG.ignore(e);}
+        }
+        _con = null;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/LoginService.java b/src/java/org/eclipse/jetty/security/LoginService.java
new file mode 100644
index 0000000..1e64141
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/LoginService.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Login Service Interface.
+ * <p>
+ * The Login service provides an abstract mechanism for an {@link Authenticator}
+ * to check credentials and to create a {@link UserIdentity} using the 
+ * set {@link IdentityService}.
+ */
+public interface LoginService
+{
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Get the name of the login service (aka Realm name)
+     */
+    String getName();
+    
+    /* ------------------------------------------------------------ */
+    /** Login a user.
+     * @param username The user name
+     * @param credentials The users credentials
+     * @return A UserIdentity if the credentials matched, otherwise null
+     */
+    UserIdentity login(String username,Object credentials);
+    
+    /* ------------------------------------------------------------ */
+    /** Validate a user identity.
+     * Validate that a UserIdentity previously created by a call 
+     * to {@link #login(String, Object)} is still valid.
+     * @param user The user to validate
+     * @return true if authentication has not been revoked for the user.
+     */
+    boolean validate(UserIdentity user);
+    
+    /* ------------------------------------------------------------ */
+    /** Get the IdentityService associated with this Login Service.
+     * @return the IdentityService associated with this Login Service.
+     */
+    IdentityService getIdentityService();
+    
+    /* ------------------------------------------------------------ */
+    /** Set the IdentityService associated with this Login Service.
+     * @param service the IdentityService associated with this Login Service.
+     */
+    void setIdentityService(IdentityService service);
+    
+    void logout(UserIdentity user);
+
+}
diff --git a/src/java/org/eclipse/jetty/security/MappedLoginService.java b/src/java/org/eclipse/jetty/security/MappedLoginService.java
new file mode 100644
index 0000000..6e3645a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/MappedLoginService.java
@@ -0,0 +1,340 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Credential;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * A login service that keeps UserIdentities in a concurrent map
+ * either as the source or a cache of the users.
+ * 
+ */
+public abstract class MappedLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(MappedLoginService.class);
+
+    protected IdentityService _identityService=new DefaultIdentityService();
+    protected String _name;
+    protected final ConcurrentMap<String, UserIdentity> _users=new ConcurrentHashMap<String, UserIdentity>();
+
+    /* ------------------------------------------------------------ */
+    protected MappedLoginService()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the name.
+     * @return the name
+     */
+    public String getName()
+    {
+        return _name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the users.
+     * @return the users
+     */
+    public ConcurrentMap<String, UserIdentity> getUsers()
+    {
+        return _users;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the name.
+     * @param name the name to set
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the users.
+     * @param users the users to set
+     */
+    public void setUsers(Map<String, UserIdentity> users)
+    {
+        if (isRunning())
+            throw new IllegalStateException("Running");
+        _users.clear();
+        _users.putAll(users);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        loadUsers();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(UserIdentity identity)
+    {   
+        LOG.debug("logout {}",identity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getSimpleName()+"["+_name+"]";
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * Called by implementations to put the user data loaded from
+     * file/db etc into the user structure.
+     * @param userName User name
+     * @param info a UserIdentity instance, or a String password or Credential instance
+     * @return User instance
+     */
+    protected synchronized UserIdentity putUser(String userName, Object info)
+    {
+        final UserIdentity identity;
+        if (info instanceof UserIdentity)
+            identity=(UserIdentity)info;
+        else
+        {
+            Credential credential = (info instanceof Credential)?(Credential)info:Credential.getCredential(info.toString());
+            
+            Principal userPrincipal = new KnownUser(userName,credential);
+            Subject subject = new Subject();
+            subject.getPrincipals().add(userPrincipal);
+            subject.getPrivateCredentials().add(credential);
+            subject.setReadOnly();
+            identity=_identityService.newUserIdentity(subject,userPrincipal,IdentityService.NO_ROLES);
+        }
+        
+        _users.put(userName,identity);
+        return identity;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put user into realm.
+     * @param userName The user to add
+     * @param credential The users Credentials
+     * @param roles The users roles
+     * @return UserIdentity
+     */
+    public synchronized UserIdentity putUser(String userName, Credential credential, String[] roles)
+    {
+        Principal userPrincipal = new KnownUser(userName,credential);
+        Subject subject = new Subject();
+        subject.getPrincipals().add(userPrincipal);
+        subject.getPrivateCredentials().add(credential);
+        
+        if (roles!=null)
+            for (String role : roles)
+                subject.getPrincipals().add(new RolePrincipal(role));
+
+        subject.setReadOnly();
+        UserIdentity identity=_identityService.newUserIdentity(subject,userPrincipal,roles);
+        _users.put(userName,identity);
+        return identity;
+    } 
+    
+    /* ------------------------------------------------------------ */
+    public void removeUser(String username)
+    {
+        _users.remove(username);
+    }   
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.LoginService#login(java.lang.String, java.lang.Object)
+     */
+    public UserIdentity login(String username, Object credentials)
+    {
+        UserIdentity user = _users.get(username);
+        
+        if (user==null)
+            user = loadUser(username);
+        
+        if (user!=null)
+        {
+            UserPrincipal principal = (UserPrincipal)user.getUserPrincipal();
+            if (principal.authenticate(credentials))
+                return user;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean validate(UserIdentity user)
+    {
+        if (_users.containsKey(user.getUserPrincipal().getName()))
+            return true;
+        
+        if (loadUser(user.getUserPrincipal().getName())!=null)
+            return true;
+                
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract UserIdentity loadUser(String username);
+    
+    /* ------------------------------------------------------------ */
+    protected abstract void loadUsers() throws IOException;
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface UserPrincipal extends Principal,Serializable
+    {
+        boolean authenticate(Object credentials);
+        public boolean isAuthenticated();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class RolePrincipal implements Principal,Serializable
+    {
+        private static final long serialVersionUID = 2998397924051854402L;
+        private final String _roleName;
+        public RolePrincipal(String name)
+        {
+            _roleName=name;
+        }
+        public String getName()
+        {
+            return _roleName;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class Anonymous implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = 1097640442553284845L;
+
+        public boolean isAuthenticated()
+        {
+            return false;
+        }
+
+        public String getName()
+        {
+            return "Anonymous";
+        }
+        
+        public boolean authenticate(Object credentials)
+        {
+            return false;
+        }
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static class KnownUser implements UserPrincipal,Serializable
+    {
+        private static final long serialVersionUID = -6226920753748399662L;
+        private final String _name;
+        private final Credential _credential;
+        
+        /* -------------------------------------------------------- */
+        public KnownUser(String name,Credential credential)
+        {
+            _name=name;
+            _credential=credential;
+        }
+
+        /* -------------------------------------------------------- */
+        public boolean authenticate(Object credentials)
+        {
+            return _credential!=null && _credential.check(credentials);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public String getName()
+        {
+            return _name;
+        }
+        
+        /* -------------------------------------------------------- */
+        public boolean isAuthenticated()
+        {
+            return true;
+        }
+
+        /* -------------------------------------------------------- */
+        @Override
+        public String toString()
+        {
+            return _name;
+        }
+    }
+}
+
diff --git a/src/java/org/eclipse/jetty/security/PropertyUserStore.java b/src/java/org/eclipse/jetty/security/PropertyUserStore.java
new file mode 100644
index 0000000..74e689f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/PropertyUserStore.java
@@ -0,0 +1,356 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.MappedLoginService.KnownUser;
+import org.eclipse.jetty.security.MappedLoginService.RolePrincipal;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.Scanner.BulkListener;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * PropertyUserStore
+ * 
+ * This class monitors a property file of the format mentioned below and notifies registered listeners of the changes to the the given file.
+ * 
+ * <PRE>
+ *  username: password [,rolename ...]
+ * </PRE>
+ * 
+ * Passwords may be clear text, obfuscated or checksummed. The class com.eclipse.Util.Password should be used to generate obfuscated passwords or password
+ * checksums.
+ * 
+ * If DIGEST Authentication is used, the password must be in a recoverable format, either plain text or OBF:.
+ */
+public class PropertyUserStore extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(PropertyUserStore.class);
+
+    private String _config;
+    private Resource _configResource;
+    private Scanner _scanner;
+    private int _refreshInterval = 0;// default is not to reload
+
+    private IdentityService _identityService = new DefaultIdentityService();
+    private boolean _firstLoad = true; // true if first load, false from that point on
+    private final List<String> _knownUsers = new ArrayList<String>();
+    private final Map<String, UserIdentity> _knownUserIdentities = new HashMap<String, UserIdentity>();
+    private List<UserListener> _listeners;
+
+    /* ------------------------------------------------------------ */
+    public String getConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setConfig(String config)
+    {
+        _config = config;
+    }
+    
+    /* ------------------------------------------------------------ */
+        public UserIdentity getUserIdentity(String userName)
+        {
+            return _knownUserIdentities.get(userName);
+        }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the resource associated with the configured properties file, creating it if necessary
+     */
+    public Resource getConfigResource() throws IOException
+    {
+        if (_configResource == null)
+        {
+            _configResource = Resource.newResource(_config);
+        }
+
+        return _configResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * sets the refresh interval (in seconds)
+     */
+    public void setRefreshInterval(int msec)
+    {
+        _refreshInterval = msec;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * refresh interval in seconds for how often the properties file should be checked for changes
+     */
+    public int getRefreshInterval()
+    {
+        return _refreshInterval;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void loadUsers() throws IOException
+    {
+        if (_config == null)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Load " + this + " from " + _config);
+        Properties properties = new Properties();
+        if (getConfigResource().exists())
+            properties.load(getConfigResource().getInputStream());
+        Set<String> known = new HashSet<String>();
+
+        for (Map.Entry<Object, Object> entry : properties.entrySet())
+        {
+            String username = ((String)entry.getKey()).trim();
+            String credentials = ((String)entry.getValue()).trim();
+            String roles = null;
+            int c = credentials.indexOf(',');
+            if (c > 0)
+            {
+                roles = credentials.substring(c + 1).trim();
+                credentials = credentials.substring(0,c).trim();
+            }
+
+            if (username != null && username.length() > 0 && credentials != null && credentials.length() > 0)
+            {
+                String[] roleArray = IdentityService.NO_ROLES;
+                if (roles != null && roles.length() > 0)
+                {
+                    roleArray = roles.split(",");
+                }
+                known.add(username);
+                Credential credential = Credential.getCredential(credentials);
+                
+                Principal userPrincipal = new KnownUser(username,credential);
+                Subject subject = new Subject();
+                subject.getPrincipals().add(userPrincipal);
+                subject.getPrivateCredentials().add(credential);
+
+                if (roles != null)
+                {
+                    for (String role : roleArray)
+                    {
+                        subject.getPrincipals().add(new RolePrincipal(role));
+                    }
+                }
+                
+                subject.setReadOnly();
+                
+                _knownUserIdentities.put(username,_identityService.newUserIdentity(subject,userPrincipal,roleArray));
+                notifyUpdate(username,credential,roleArray);
+            }
+        }
+
+        synchronized (_knownUsers)
+        {
+            /*
+             * if its not the initial load then we want to process removed users
+             */
+            if (!_firstLoad)
+            {
+                Iterator<String> users = _knownUsers.iterator();
+                while (users.hasNext())
+                {
+                    String user = users.next();
+                    if (!known.contains(user))
+                    {
+                        _knownUserIdentities.remove(user);
+                        notifyRemove(user);
+                    }
+                }
+            }
+
+            /*
+             * reset the tracked _users list to the known users we just processed
+             */
+
+            _knownUsers.clear();
+            _knownUsers.addAll(known);
+
+        }
+
+        /*
+         * set initial load to false as there should be no more initial loads
+         */
+        _firstLoad = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Depending on the value of the refresh interval, this method will either start up a scanner thread that will monitor the properties file for changes after
+     * it has initially loaded it. Otherwise the users will be loaded and there will be no active monitoring thread so changes will not be detected.
+     * 
+     * 
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (getRefreshInterval() > 0)
+        {
+            _scanner = new Scanner();
+            _scanner.setScanInterval(getRefreshInterval());
+            List<File> dirList = new ArrayList<File>(1);
+            dirList.add(getConfigResource().getFile().getParentFile());
+            _scanner.setScanDirs(dirList);
+            _scanner.setFilenameFilter(new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+                {
+                    File f = new File(dir,name);
+                    try
+                    {
+                        if (f.compareTo(getConfigResource().getFile()) == 0)
+                        {
+                            return true;
+                        }
+                    }
+                    catch (IOException e)
+                    {
+                        return false;
+                    }
+
+                    return false;
+                }
+
+            });
+
+            _scanner.addListener(new BulkListener()
+            {
+                public void filesChanged(List<String> filenames) throws Exception
+                {
+                    if (filenames == null)
+                        return;
+                    if (filenames.isEmpty())
+                        return;
+                    if (filenames.size() == 1)
+                    {
+                        Resource r = Resource.newResource(filenames.get(0));
+                        if (r.getFile().equals(_configResource.getFile()))
+                            loadUsers();
+                    }
+                }
+
+                public String toString()
+                {
+                    return "PropertyUserStore$Scanner";
+                }
+
+            });
+
+            _scanner.setReportExistingFilesOnStartup(true);
+            _scanner.setRecursive(false);
+            _scanner.start();
+        }
+        else
+        {
+            loadUsers();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_scanner != null)
+            _scanner.stop();
+        _scanner = null;
+    }
+
+    /**
+     * Notifies the registered listeners of potential updates to a user
+     * 
+     * @param username
+     * @param credential
+     * @param roleArray
+     */
+    private void notifyUpdate(String username, Credential credential, String[] roleArray)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().update(username,credential,roleArray);
+            }
+        }
+    }
+
+    /**
+     * notifies the registered listeners that a user has been removed.
+     * 
+     * @param username
+     */
+    private void notifyRemove(String username)
+    {
+        if (_listeners != null)
+        {
+            for (Iterator<UserListener> i = _listeners.iterator(); i.hasNext();)
+            {
+                i.next().remove(username);
+            }
+        }
+    }
+
+    /**
+     * registers a listener to be notified of the contents of the property file
+     */
+    public void registerUserListener(UserListener listener)
+    {
+        if (_listeners == null)
+        {
+            _listeners = new ArrayList<UserListener>();
+        }
+        _listeners.add(listener);
+    }
+
+    /**
+     * UserListener
+     */
+    public interface UserListener
+    {
+        public void update(String username, Credential credential, String[] roleArray);
+
+        public void remove(String username);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/RoleInfo.java b/src/java/org/eclipse/jetty/security/RoleInfo.java
new file mode 100644
index 0000000..730995f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/RoleInfo.java
@@ -0,0 +1,143 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * 
+ * Badly named class that holds the role and user data constraint info for a
+ * path/http method combination, extracted and combined from security
+ * constraints.
+ * 
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class RoleInfo
+{
+    private boolean _isAnyRole;
+    private boolean _checked;
+    private boolean _forbidden;
+    private UserDataConstraint _userDataConstraint;
+
+    private final Set<String> _roles = new CopyOnWriteArraySet<String>();
+
+    public RoleInfo()
+    {    
+    }
+    
+    public boolean isChecked()
+    {
+        return _checked;
+    }
+
+    public void setChecked(boolean checked)
+    {
+        this._checked = checked;
+        if (!checked)
+        {
+            _forbidden=false;
+            _roles.clear();
+            _isAnyRole=false;
+        }
+    }
+
+    public boolean isForbidden()
+    {
+        return _forbidden;
+    }
+
+    public void setForbidden(boolean forbidden)
+    {
+        this._forbidden = forbidden;
+        if (forbidden)
+        {
+            _checked = true;
+            _userDataConstraint = null;
+            _isAnyRole=false;
+            _roles.clear();
+        }
+    }
+
+    public boolean isAnyRole()
+    {
+        return _isAnyRole;
+    }
+
+    public void setAnyRole(boolean anyRole)
+    {
+        this._isAnyRole=anyRole;
+        if (anyRole)
+        {
+            _checked = true;
+            _roles.clear();
+        }
+    }
+
+    public UserDataConstraint getUserDataConstraint()
+    {
+        return _userDataConstraint;
+    }
+
+    public void setUserDataConstraint(UserDataConstraint userDataConstraint)
+    {
+        if (userDataConstraint == null) throw new NullPointerException("Null UserDataConstraint");
+        if (this._userDataConstraint == null)
+        {
+            this._userDataConstraint = userDataConstraint;
+        }
+        else
+        {
+            this._userDataConstraint = this._userDataConstraint.combine(userDataConstraint);
+        }
+    }
+
+    public Set<String> getRoles()
+    {
+        return _roles;
+    }
+    
+    public void addRole(String role)
+    {
+        _roles.add(role);
+    }
+
+    public void combine(RoleInfo other)
+    {
+        if (other._forbidden)
+            setForbidden(true);
+        else if (!other._checked) // TODO is this the right way around???
+            setChecked(true);
+        else if (other._isAnyRole)
+            setAnyRole(true);
+        else if (!_isAnyRole)
+        {
+            for (String r : other._roles)
+                _roles.add(r);
+        }
+        
+        setUserDataConstraint(other._userDataConstraint);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return "{RoleInfo"+(_forbidden?",F":"")+(_checked?",C":"")+(_isAnyRole?",*":_roles)+"}";
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/RoleRunAsToken.java b/src/java/org/eclipse/jetty/security/RoleRunAsToken.java
new file mode 100644
index 0000000..13a7ea7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/RoleRunAsToken.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+
+
+/**
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public class RoleRunAsToken implements RunAsToken
+{
+    private final String _runAsRole;
+
+    public RoleRunAsToken(String runAsRole)
+    {
+        this._runAsRole = runAsRole;
+    }
+
+    public String getRunAsRole()
+    {
+        return _runAsRole;
+    }
+
+    public String toString()
+    {
+        return "RoleRunAsToken("+_runAsRole+")";
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/RunAsToken.java b/src/java/org/eclipse/jetty/security/RunAsToken.java
new file mode 100644
index 0000000..639c972
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/RunAsToken.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * marker interface for run-as-role tokens
+ * @version $Rev: 4701 $ $Date: 2009-03-03 13:01:26 +0100 (Tue, 03 Mar 2009) $
+ */
+public interface RunAsToken
+{
+}
diff --git a/src/java/org/eclipse/jetty/security/SecurityHandler.java b/src/java/org/eclipse/jetty/security/SecurityHandler.java
new file mode 100644
index 0000000..4e55183
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/SecurityHandler.java
@@ -0,0 +1,685 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Abstract SecurityHandler.
+ * Select and apply an {@link Authenticator} to a request.
+ * <p>
+ * The Authenticator may either be directly set on the handler
+ * or will be create during {@link #start()} with a call to
+ * either the default or set AuthenticatorFactory.
+ * <p>
+ * SecurityHandler has a set of initparameters that are used by the 
+ * Authentication.Configuration. At startup, any context init parameters
+ * that start with "org.eclipse.jetty.security." that do not have 
+ * values in the SecurityHandler init parameters, are copied.  
+ * 
+ */
+public abstract class SecurityHandler extends HandlerWrapper implements Authenticator.AuthConfiguration
+{
+    private static final Logger LOG = Log.getLogger(SecurityHandler.class);
+
+    /* ------------------------------------------------------------ */
+    private boolean _checkWelcomeFiles = false;
+    private Authenticator _authenticator;
+    private Authenticator.Factory _authenticatorFactory=new DefaultAuthenticatorFactory();
+    private String _realmName;
+    private String _authMethod;
+    private final Map<String,String> _initParameters=new HashMap<String,String>();
+    private LoginService _loginService;
+    private boolean _loginServiceShared;
+    private IdentityService _identityService;
+    private boolean _renewSession=true;
+
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the identityService.
+     * @return the identityService
+     */
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the identityService.
+     * @param identityService the identityService to set
+     */
+    public void setIdentityService(IdentityService identityService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        _identityService = identityService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the loginService.
+     * @return the loginService
+     */
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the loginService.
+     * @param loginService the loginService to set
+     */
+    public void setLoginService(LoginService loginService)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        _loginService = loginService;
+        _loginServiceShared=false;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Authenticator getAuthenticator()
+    {
+        return _authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the authenticator.
+     * @param authenticator
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticator(Authenticator authenticator)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+        _authenticator = authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authenticatorFactory
+     */
+    public Authenticator.Factory getAuthenticatorFactory()
+    {
+        return _authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticatorFactory the authenticatorFactory to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthenticatorFactory(Authenticator.Factory authenticatorFactory)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _authenticatorFactory = authenticatorFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the realmName
+     */
+    public String getRealmName()
+    {
+        return _realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param realmName the realmName to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setRealmName(String realmName)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _realmName = realmName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the authMethod
+     */
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authMethod the authMethod to set
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setAuthMethod(String authMethod)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _authMethod = authMethod;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if forwards to welcome files are authenticated
+     */
+    public boolean isCheckWelcomeFiles()
+    {
+        return _checkWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticateWelcomeFiles True if forwards to welcome files are
+     *                authenticated
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public void setCheckWelcomeFiles(boolean authenticateWelcomeFiles)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        _checkWelcomeFiles = authenticateWelcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getInitParameter(String key)
+    {
+        return _initParameters.get(key);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> getInitParameterNames()
+    {
+        return _initParameters.keySet();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set an initialization parameter.
+     * @param key
+     * @param value
+     * @return previous value
+     * @throws IllegalStateException if the SecurityHandler is running
+     */
+    public String setInitParameter(String key, String value)
+    {
+        if (isRunning())
+            throw new IllegalStateException("running");
+        return _initParameters.put(key,value);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected LoginService findLoginService()
+    {
+        List<LoginService> list = getServer().getBeans(LoginService.class);
+        
+        String realm=getRealmName();
+        if (realm!=null)
+        {
+            for (LoginService service : list)
+                if (service.getName()!=null && service.getName().equals(realm))
+                    return service;
+        }
+        else if (list.size()==1)
+            return list.get(0);
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected IdentityService findIdentityService()
+    {
+        return getServer().getBean(IdentityService.class);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    protected void doStart()
+        throws Exception
+    {
+        // copy security init parameters
+        ContextHandler.Context context =ContextHandler.getCurrentContext();
+        if (context!=null)
+        {
+            Enumeration<String> names=context.getInitParameterNames();
+            while (names!=null && names.hasMoreElements())
+            {
+                String name =names.nextElement();
+                if (name.startsWith("org.eclipse.jetty.security.") &&
+                        getInitParameter(name)==null)
+                    setInitParameter(name,context.getInitParameter(name));
+            }
+            
+            //register a session listener to handle securing sessions when authentication is performed
+            context.getContextHandler().addEventListener(new HttpSessionListener()
+            {
+                
+                public void sessionDestroyed(HttpSessionEvent se)
+                {
+                   
+                }
+                
+                public void sessionCreated(HttpSessionEvent se)
+                {
+                    //if current request is authenticated, then as we have just created the session, mark it as secure, as it has not yet been returned to a user
+                    AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
+                    if (connection == null)
+                        return;
+                    Request request = connection.getRequest();
+                    if (request == null)
+                        return;
+                    
+                    if (request.isSecure())
+                    {
+                        se.getSession().setAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+                    }
+                }
+            });
+        }
+        
+        // complicated resolution of login and identity service to handle
+        // many different ways these can be constructed and injected.
+        
+        if (_loginService==null)
+        {
+            _loginService=findLoginService();
+            if (_loginService!=null)
+                _loginServiceShared=true;
+        }
+        
+        if (_identityService==null)
+        {
+           
+            if (_loginService!=null)
+                _identityService=_loginService.getIdentityService();
+
+            if (_identityService==null)
+                _identityService=findIdentityService();
+            
+            if (_identityService==null && _realmName!=null)
+                _identityService=new DefaultIdentityService();
+        }
+        
+        if (_loginService!=null)
+        {
+            if (_loginService.getIdentityService()==null)
+                _loginService.setIdentityService(_identityService);
+            else if (_loginService.getIdentityService()!=_identityService)
+                throw new IllegalStateException("LoginService has different IdentityService to "+this);
+        }
+
+        if (!_loginServiceShared && _loginService instanceof LifeCycle)
+            ((LifeCycle)_loginService).start();        
+        
+        if (_authenticator==null && _authenticatorFactory!=null && _identityService!=null)
+        {
+            _authenticator=_authenticatorFactory.getAuthenticator(getServer(),ContextHandler.getCurrentContext(),this, _identityService, _loginService);
+            if (_authenticator!=null)
+                _authMethod=_authenticator.getAuthMethod();
+        }
+
+        if (_authenticator==null)
+        {
+            if (_realmName!=null)
+            {
+                LOG.warn("No ServerAuthentication for "+this);
+                throw new IllegalStateException("No ServerAuthentication");
+            }
+        }
+        else
+        {
+            _authenticator.setConfiguration(this);
+            if (_authenticator instanceof LifeCycle)
+                ((LifeCycle)_authenticator).start();
+        }
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        
+        if (!_loginServiceShared && _loginService instanceof LifeCycle)
+            ((LifeCycle)_loginService).stop();
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean checkSecurity(Request request)
+    {
+        switch(request.getDispatcherType())
+        {
+            case REQUEST:
+            case ASYNC:
+                return true;
+            case FORWARD:
+                if (_checkWelcomeFiles && request.getAttribute("org.eclipse.jetty.server.welcome") != null)
+                {
+                    request.removeAttribute("org.eclipse.jetty.server.welcome");
+                    return true;
+                }
+                return false;
+            default:
+                return false;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    public boolean isSessionRenewedOnAuthentication()
+    {
+        return _renewSession;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set renew the session on Authentication.
+     * <p>
+     * If set to true, then on authentication, the session associated with a reqeuest is invalidated and replaced with a new session.
+     * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication()
+     */
+    public void setSessionRenewedOnAuthentication(boolean renew)
+    {
+        _renewSession=renew;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String,
+     *      javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String pathInContext, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Handler handler=getHandler();
+        
+        if (handler==null)
+            return;
+
+        final Authenticator authenticator = _authenticator;
+        
+        if (checkSecurity(baseRequest))
+        {
+            Object constraintInfo = prepareConstraintInfo(pathInContext, baseRequest);
+            
+            // Check data constraints
+            if (!checkUserDataPermissions(pathInContext, baseRequest, base_response, constraintInfo))
+            {
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(Response.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+
+            // is Auth mandatory?
+            boolean isAuthMandatory = 
+                isAuthMandatory(baseRequest, base_response, constraintInfo);
+
+            if (isAuthMandatory && authenticator==null)
+            {
+                LOG.warn("No authenticator for: "+constraintInfo);
+                if (!baseRequest.isHandled())
+                {
+                    response.sendError(Response.SC_FORBIDDEN);
+                    baseRequest.setHandled(true);
+                }
+                return;
+            }
+            
+            // check authentication
+            Object previousIdentity = null;
+            try
+            {
+                Authentication authentication = baseRequest.getAuthentication();
+                if (authentication==null || authentication==Authentication.NOT_CHECKED)
+                    authentication=authenticator==null?Authentication.UNAUTHENTICATED:authenticator.validateRequest(request, response, isAuthMandatory);
+
+                if (authentication instanceof Authentication.Wrapped)
+                {
+                    request=((Authentication.Wrapped)authentication).getHttpServletRequest();
+                    response=((Authentication.Wrapped)authentication).getHttpServletResponse();
+                }
+
+                if (authentication instanceof Authentication.ResponseSent)
+                {
+                    baseRequest.setHandled(true);
+                }
+                else if (authentication instanceof Authentication.User)
+                {
+                    Authentication.User userAuth = (Authentication.User)authentication;
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(userAuth.getUserIdentity());
+
+                    if (isAuthMandatory)
+                    {
+                        boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, constraintInfo, userAuth.getUserIdentity());
+                        if (!authorized)
+                        {
+                            response.sendError(Response.SC_FORBIDDEN, "!role");
+                            baseRequest.setHandled(true);
+                            return;
+                        }
+                    }
+                         
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                }
+                else if (authentication instanceof Authentication.Deferred)
+                {
+                    DeferredAuthentication deferred= (DeferredAuthentication)authentication;
+                    baseRequest.setAuthentication(authentication);
+
+                    try
+                    {
+                        handler.handle(pathInContext, baseRequest, request, response);
+                    }
+                    finally
+                    {
+                        previousIdentity = deferred.getPreviousAssociation();
+                    }
+
+                    if (authenticator!=null)
+                    {
+                        Authentication auth=baseRequest.getAuthentication();
+                        if (auth instanceof Authentication.User)
+                        {
+                            Authentication.User userAuth = (Authentication.User)auth;
+                            authenticator.secureResponse(request, response, isAuthMandatory, userAuth);
+                        }
+                        else
+                            authenticator.secureResponse(request, response, isAuthMandatory, null);
+                    }
+                }
+                else
+                {
+                    baseRequest.setAuthentication(authentication);
+                    if (_identityService!=null)
+                        previousIdentity = _identityService.associate(null);
+                    handler.handle(pathInContext, baseRequest, request, response);
+                    if (authenticator!=null)
+                        authenticator.secureResponse(request, response, isAuthMandatory, null);
+                }
+            }
+            catch (ServerAuthException e)
+            {
+                // jaspi 3.8.3 send HTTP 500 internal server error, with message
+                // from AuthException
+                response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.disassociate(previousIdentity);
+            }
+        }
+        else
+            handler.handle(pathInContext, baseRequest, request, response);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static SecurityHandler getCurrentSecurityHandler()
+    {
+        Context context = ContextHandler.getCurrentContext();
+        if (context==null)
+            return null;
+        
+        SecurityHandler security = context.getContextHandler().getChildHandlerByClass(SecurityHandler.class);
+        return security;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout(Authentication.User user)
+    {
+        LOG.debug("logout {}",user);
+        LoginService login_service=getLoginService();
+        if (login_service!=null)
+        {
+            login_service.logout(user.getUserIdentity());
+        }
+        
+        IdentityService identity_service=getIdentityService();
+        if (identity_service!=null)
+        {
+            // TODO recover previous from threadlocal (or similar)
+            Object previous=null;
+            identity_service.disassociate(previous);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected abstract Object prepareConstraintInfo(String pathInContext, Request request);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo);
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo,
+                                                           UserIdentity userIdentity) throws IOException;
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class NotChecked implements Principal
+    {
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "NOT CHECKED";
+        }
+
+        public SecurityHandler getSecurityHandler()
+        {
+            return SecurityHandler.this;
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public static Principal __NO_USER = new Principal()
+    {
+        public String getName()
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "No User";
+        }
+    };
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Nobody user. The Nobody UserPrincipal is used to indicate a partial state
+     * of authentication. A request with a Nobody UserPrincipal will be allowed
+     * past all authentication constraints - but will not be considered an
+     * authenticated request. It can be used by Authenticators such as
+     * FormAuthenticator to allow access to logon and error pages within an
+     * authenticated URI tree.
+     */
+    public static Principal __NOBODY = new Principal()
+    {
+        public String getName()
+        {
+            return "Nobody";
+        }
+
+        @Override
+        public String toString()
+        {
+            return getName();
+        }
+    };
+
+}
diff --git a/src/java/org/eclipse/jetty/security/ServerAuthException.java b/src/java/org/eclipse/jetty/security/ServerAuthException.java
new file mode 100644
index 0000000..85f532f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/ServerAuthException.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public class ServerAuthException extends GeneralSecurityException
+{
+
+    public ServerAuthException()
+    {
+    }
+
+    public ServerAuthException(String s)
+    {
+        super(s);
+    }
+
+    public ServerAuthException(String s, Throwable throwable)
+    {
+        super(s, throwable);
+    }
+
+    public ServerAuthException(Throwable throwable)
+    {
+        super(throwable);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/SpnegoLoginService.java b/src/java/org/eclipse/jetty/security/SpnegoLoginService.java
new file mode 100644
index 0000000..2a2a5ad
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/SpnegoLoginService.java
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.util.Properties;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.B64Code;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+public class SpnegoLoginService extends AbstractLifeCycle implements LoginService
+{
+    private static final Logger LOG = Log.getLogger(SpnegoLoginService.class);
+
+    protected IdentityService _identityService;// = new LdapIdentityService();
+    protected String _name;
+    private String _config;
+    
+    private String _targetName;
+
+    public SpnegoLoginService()
+    {
+        
+    }
+    
+    public SpnegoLoginService( String name )
+    {
+        setName(name);
+    }
+    
+    public SpnegoLoginService( String name, String config )
+    {
+        setName(name);
+        setConfig(config);
+    }
+    
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setName(String name)
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+        
+        _name = name;
+    }
+    
+    public String getConfig()
+    {
+        return _config;
+    }
+    
+    public void setConfig( String config )
+    {
+        if (isRunning())
+        {
+            throw new IllegalStateException("Running");
+        }
+        
+        _config = config;
+    }
+    
+    
+    
+    @Override
+    protected void doStart() throws Exception
+    {
+        Properties properties = new Properties();
+        Resource resource = Resource.newResource(_config);
+        properties.load(resource.getInputStream());
+        
+        _targetName = properties.getProperty("targetName");
+        
+        LOG.debug("Target Name {}", _targetName);
+        
+        super.doStart();
+    }
+
+    /**
+     * username will be null since the credentials will contain all the relevant info
+     */
+    public UserIdentity login(String username, Object credentials)
+    {
+        String encodedAuthToken = (String)credentials;
+        
+        byte[] authToken = B64Code.decode(encodedAuthToken);
+        
+        GSSManager manager = GSSManager.getInstance();
+        try
+        {
+            Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+            GSSName gssName = manager.createName(_targetName,null);
+            GSSCredential serverCreds = manager.createCredential(gssName,GSSCredential.INDEFINITE_LIFETIME,krb5Oid,GSSCredential.ACCEPT_ONLY);
+            GSSContext gContext = manager.createContext(serverCreds);
+
+            if (gContext == null)
+            {
+                LOG.debug("SpnegoUserRealm: failed to establish GSSContext");
+            }
+            else
+            {
+                while (!gContext.isEstablished())
+                {
+                    authToken = gContext.acceptSecContext(authToken,0,authToken.length);
+                }
+                if (gContext.isEstablished())
+                {
+                    String clientName = gContext.getSrcName().toString();
+                    String role = clientName.substring(clientName.indexOf('@') + 1);
+                    
+                    LOG.debug("SpnegoUserRealm: established a security context");
+                    LOG.debug("Client Principal is: " + gContext.getSrcName());
+                    LOG.debug("Server Principal is: " + gContext.getTargName());
+                    LOG.debug("Client Default Role: " + role);
+
+                    SpnegoUserPrincipal user = new SpnegoUserPrincipal(clientName,authToken);
+
+                    Subject subject = new Subject();
+                    subject.getPrincipals().add(user);
+                    
+                    return _identityService.newUserIdentity(subject,user, new String[]{role});
+                }
+            }
+
+        }
+        catch (GSSException gsse)
+        {
+            LOG.warn(gsse);
+        }
+
+        return null;
+    }
+
+    public boolean validate(UserIdentity user)
+    {
+        return false;
+    }
+
+    public IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+
+    public void setIdentityService(IdentityService service)
+    {
+        _identityService = service;
+    }
+
+	public void logout(UserIdentity user) {
+		// TODO Auto-generated method stub
+		
+	}
+
+}
diff --git a/src/java/org/eclipse/jetty/security/SpnegoUserIdentity.java b/src/java/org/eclipse/jetty/security/SpnegoUserIdentity.java
new file mode 100644
index 0000000..fd93b1a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/SpnegoUserIdentity.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+import java.util.List;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.server.UserIdentity;
+
+public class SpnegoUserIdentity implements UserIdentity
+{
+    private Subject _subject;
+    private Principal _principal;
+    private List<String> _roles;
+    
+    public SpnegoUserIdentity( Subject subject, Principal principal, List<String> roles )
+    {
+        _subject = subject;
+        _principal = principal;
+        _roles = roles;
+    }
+    
+    
+    public Subject getSubject()
+    {
+        return _subject;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return _principal;
+    }
+
+    public boolean isUserInRole(String role, Scope scope)
+    {
+        return _roles.contains(role);
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java b/src/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java
new file mode 100644
index 0000000..cedd370
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/SpnegoUserPrincipal.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import java.security.Principal;
+
+import org.eclipse.jetty.util.security.B64Code;
+
+public class SpnegoUserPrincipal implements Principal
+{
+    private final String _name;
+    private byte[] _token;
+    private String _encodedToken;
+    
+    public SpnegoUserPrincipal( String name, String encodedToken )
+    {
+        _name = name;
+        _encodedToken = encodedToken;
+    }
+    
+    public SpnegoUserPrincipal( String name, byte[] token )
+    {
+        _name = name;
+        _token = token;
+    }
+    
+    public String getName()
+    {
+        return _name;
+    }
+
+    public byte[] getToken()
+    {
+        if ( _token == null )
+        {
+            _token = B64Code.decode(_encodedToken);
+        }
+        return _token;
+    }
+    
+    public String getEncodedToken()
+    {
+        if ( _encodedToken == null )
+        {
+            _encodedToken = new String(B64Code.encode(_token,true));
+        }
+        return _encodedToken;
+    }   
+}
diff --git a/src/java/org/eclipse/jetty/security/UserAuthentication.java b/src/java/org/eclipse/jetty/security/UserAuthentication.java
new file mode 100644
index 0000000..e84fa74
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/UserAuthentication.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class UserAuthentication implements Authentication.User
+{
+    private final String _method;
+    private final UserIdentity _userIdentity;
+
+    public UserAuthentication(String method, UserIdentity userIdentity)
+    {
+        _method = method;
+        _userIdentity = userIdentity;
+    }
+    
+    public String getAuthMethod()
+    {
+        return _method;
+    }
+
+    public UserIdentity getUserIdentity()
+    {
+        return _userIdentity;
+    }
+
+    public boolean isUserInRole(Scope scope, String role)
+    {
+        return _userIdentity.isUserInRole(role, scope);
+    }
+    
+    @Override
+    public String toString()
+    {
+        return "{User,"+getAuthMethod()+","+_userIdentity+"}";
+    }
+
+    public void logout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/UserDataConstraint.java b/src/java/org/eclipse/jetty/security/UserDataConstraint.java
new file mode 100644
index 0000000..c288e1d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/UserDataConstraint.java
@@ -0,0 +1,40 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security;
+
+/**
+ * @version $Rev: 4466 $ $Date: 2009-02-10 23:42:54 +0100 (Tue, 10 Feb 2009) $
+ */
+public enum UserDataConstraint
+{
+    None, Integral, Confidential;
+
+    public static UserDataConstraint get(int dataConstraint)
+    {
+        if (dataConstraint < -1 || dataConstraint > 2) throw new IllegalArgumentException("Expected -1, 0, 1, or 2, not: " + dataConstraint);
+        if (dataConstraint == -1) return None;
+        return values()[dataConstraint];
+    }
+
+    public UserDataConstraint combine(UserDataConstraint other)
+    {
+        if (this.compareTo(other) < 0) return this;
+        return other;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
new file mode 100644
index 0000000..f9a6c7c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/BasicAuthenticator.java
@@ -0,0 +1,118 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class BasicAuthenticator extends LoginAuthenticator 
+{   
+    /* ------------------------------------------------------------ */
+    public BasicAuthenticator()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#getAuthMethod()
+     */
+    public String getAuthMethod()
+    {
+        return Constraint.__BASIC_AUTH;
+    }
+
+ 
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.Authenticator#validateRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, boolean)
+     */
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+
+        try
+        {
+            if (!mandatory)
+                return new DeferredAuthentication(this);
+
+            if (credentials != null)
+            {                 
+                int space=credentials.indexOf(' ');
+                if (space>0)
+                {
+                    String method=credentials.substring(0,space);
+                    if ("basic".equalsIgnoreCase(method))
+                    {
+                        credentials = credentials.substring(space+1);
+                        credentials = B64Code.decode(credentials,StringUtil.__ISO_8859_1);
+                        int i = credentials.indexOf(':');
+                        if (i>0)
+                        {
+                            String username = credentials.substring(0,i);
+                            String password = credentials.substring(i+1);
+
+                            UserIdentity user = login (username, password, request);
+                            if (user!=null)
+                            {
+                                return new UserAuthentication(getAuthMethod(),user);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (DeferredAuthentication.isDeferred(response))
+                return Authentication.UNAUTHENTICATED;
+            
+            response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "basic realm=\"" + _loginService.getName() + '"');
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return Authentication.SEND_CONTINUE;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
new file mode 100644
index 0000000..7c24fcc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/ClientCertAuthenticator.java
@@ -0,0 +1,368 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.cert.CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Password;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class ClientCertAuthenticator extends LoginAuthenticator
+{
+    /** String name of keystore password property. */
+    private static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+    
+    public ClientCertAuthenticator()
+    {
+        super();
+    }
+
+    public String getAuthMethod()
+    {
+        return Constraint.__CERT_AUTH;
+    }
+    
+    
+
+    /**
+     * @return Authentication for request
+     * @throws ServerAuthException
+     */
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
+
+        try
+        {
+            // Need certificates.
+            if (certs != null && certs.length > 0)
+            {
+                
+                if (_validateCerts)
+                {
+                    KeyStore trustStore = getKeyStore(null,
+                            _trustStorePath, _trustStoreType, _trustStoreProvider,
+                            _trustStorePassword == null ? null :_trustStorePassword.toString());
+                    Collection<? extends CRL> crls = loadCRL(_crlPath);
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.validate(certs);
+                }
+                
+                for (X509Certificate cert: certs)
+                {
+                    if (cert==null)
+                        continue;
+
+                    Principal principal = cert.getSubjectDN();
+                    if (principal == null) principal = cert.getIssuerDN();
+                    final String username = principal == null ? "clientcert" : principal.getName();
+
+                    final char[] credential = B64Code.encode(cert.getSignature());
+
+                    UserIdentity user = login(username, credential, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return Authentication.SEND_FAILURE;
+            }
+            
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (Exception e)
+        {
+            throw new ServerAuthException(e.getMessage());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads keystore using an input stream or a file path in the same
+     * order of precedence.
+     *
+     * Required for integrations to be able to override the mechanism
+     * used to load a keystore in order to provide their own implementation.
+     *
+     * @param storeStream keystore input stream
+     * @param storePath path of keystore file
+     * @param storeType keystore type
+     * @param storeProvider keystore provider
+     * @param storePassword keystore password
+     * @return created keystore
+     * @throws Exception
+     */
+    protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return a (possibly empty) collection view of java.security.cert.CRL objects initialized with the data from the
+     *         input stream.
+     * @throws Exception
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        _validateCerts = validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStore(String trustStorePath)
+    {
+        _trustStorePath = trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        _trustStoreType = trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the crlPath.
+     * @return the crlPath
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the crlPath.
+     * @param crlPath the crlPath to set
+     */
+    public void setCrlPath(String crlPath)
+    {
+        _crlPath = crlPath;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/src/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
new file mode 100644
index 0000000..854c62d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java
@@ -0,0 +1,335 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class DeferredAuthentication implements Authentication.Deferred
+{
+    private static final Logger LOG = Log.getLogger(DeferredAuthentication.class);
+    protected final LoginAuthenticator _authenticator;
+    private Object _previousAssociation;
+
+    /* ------------------------------------------------------------ */
+    public DeferredAuthentication(LoginAuthenticator authenticator)
+    {
+        if (authenticator == null)
+            throw new NullPointerException("No Authenticator");
+        this._authenticator = authenticator;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(ServletRequest)
+     */
+    public Authentication authenticate(ServletRequest request)
+    {
+        try
+        {
+            Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true);
+            
+            if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent))
+            {
+                LoginService login_service= _authenticator.getLoginService();
+                IdentityService identity_service=login_service.getIdentityService();
+                
+                if (identity_service!=null)
+                    _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+                
+                return authentication;
+            }
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+
+        return this;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#authenticate(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public Authentication authenticate(ServletRequest request, ServletResponse response)
+    {
+        try
+        {
+            LoginService login_service= _authenticator.getLoginService();
+            IdentityService identity_service=login_service.getIdentityService();
+            
+            Authentication authentication = _authenticator.validateRequest(request,response,true);
+            if (authentication instanceof Authentication.User && identity_service!=null)
+                _previousAssociation=identity_service.associate(((Authentication.User)authentication).getUserIdentity());
+            return authentication;
+        }
+        catch (ServerAuthException e)
+        {
+            LOG.debug(e);
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.Authentication.Deferred#login(java.lang.String, java.lang.String)
+     */
+    public Authentication login(String username, Object password, ServletRequest request)
+    {
+        UserIdentity identity = _authenticator.login(username, password, request);
+        if (identity != null)
+        {
+            IdentityService identity_service = _authenticator.getLoginService().getIdentityService();
+            UserAuthentication authentication = new UserAuthentication("API",identity);
+            if (identity_service != null)
+                _previousAssociation=identity_service.associate(identity);
+            return authentication;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getPreviousAssociation()
+    {
+        return _previousAssociation;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param response
+     * @return true if this response is from a deferred call to {@link #authenticate(ServletRequest)}
+     */
+    public static boolean isDeferred(HttpServletResponse response)
+    {
+        return response==__deferredResponse;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    final static HttpServletResponse __deferredResponse = new HttpServletResponse()
+    {
+        public void addCookie(Cookie cookie)
+        {
+        }
+
+        public void addDateHeader(String name, long date)
+        {
+        }
+
+        public void addHeader(String name, String value)
+        {
+        }
+
+        public void addIntHeader(String name, int value)
+        {
+        }
+
+        public boolean containsHeader(String name)
+        {
+            return false;
+        }
+
+        public String encodeRedirectURL(String url)
+        {
+            return null;
+        }
+
+        public String encodeRedirectUrl(String url)
+        {
+            return null;
+        }
+
+        public String encodeURL(String url)
+        {
+            return null;
+        }
+
+        public String encodeUrl(String url)
+        {
+            return null;
+        }
+
+        public void sendError(int sc) throws IOException
+        {
+        }
+
+        public void sendError(int sc, String msg) throws IOException
+        {
+        }
+
+        public void sendRedirect(String location) throws IOException
+        {
+        }
+
+        public void setDateHeader(String name, long date)
+        {
+        }
+
+        public void setHeader(String name, String value)
+        {
+        }
+
+        public void setIntHeader(String name, int value)
+        {
+        }
+
+        public void setStatus(int sc)
+        {
+        }
+
+        public void setStatus(int sc, String sm)
+        {
+        }
+
+        public void flushBuffer() throws IOException
+        {
+        }
+
+        public int getBufferSize()
+        {
+            return 1024;
+        }
+
+        public String getCharacterEncoding()
+        {
+            return null;
+        }
+
+        public String getContentType()
+        {
+            return null;
+        }
+
+        public Locale getLocale()
+        {
+            return null;
+        }
+
+        public ServletOutputStream getOutputStream() throws IOException
+        {
+            return __nullOut;
+        }
+
+        public PrintWriter getWriter() throws IOException
+        {
+            return IO.getNullPrintWriter();
+        }
+
+        public boolean isCommitted()
+        {
+            return true;
+        }
+
+        public void reset()
+        {
+        }
+
+        public void resetBuffer()
+        {
+        }
+
+        public void setBufferSize(int size)
+        {
+        }
+
+        public void setCharacterEncoding(String charset)
+        {
+        }
+
+        public void setContentLength(int len)
+        {
+        }
+
+        public void setContentType(String type)
+        {
+        }
+
+        public void setLocale(Locale loc)
+        {
+        }
+
+	public Collection<String> getHeaderNames()
+	{
+	    return Collections.emptyList();
+	}
+
+	@Override
+	public String getHeader(String arg0)
+	{
+	    return null;
+	}
+
+	@Override
+	public Collection<String> getHeaders(String arg0)
+	{
+            return Collections.emptyList();
+	}
+
+	@Override
+	public int getStatus()
+	{
+	    return 0;
+	}
+
+    };
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static ServletOutputStream __nullOut = new ServletOutputStream()
+    {
+        public void write(int b) throws IOException
+        {
+        }
+
+        public void print(String s) throws IOException
+        {
+        }
+
+        public void println(String s) throws IOException
+        {
+        }
+    };
+
+    
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
new file mode 100644
index 0000000..4cd279e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/DigestAuthenticator.java
@@ -0,0 +1,412 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.BitSet;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.util.security.Credential;
+
+/**
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ * 
+ * The nonce max age in ms can be set with the {@link SecurityHandler#setInitParameter(String, String)} 
+ * using the name "maxNonceAge"
+ */
+public class DigestAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(DigestAuthenticator.class);
+    SecureRandom _random = new SecureRandom();
+    private long _maxNonceAgeMs = 60*1000;
+    private int _maxNC=1024;
+    private ConcurrentMap<String, Nonce> _nonceMap = new ConcurrentHashMap<String, Nonce>();
+    private Queue<Nonce> _nonceQueue = new ConcurrentLinkedQueue<Nonce>();
+    private static class Nonce
+    {
+        final String _nonce;
+        final long _ts;
+        final BitSet _seen; 
+
+        public Nonce(String nonce, long ts, int size)
+        {
+            _nonce=nonce;
+            _ts=ts;
+            _seen = new BitSet(size);
+        }
+
+        public boolean seen(int count)
+        {
+            synchronized (this)
+            {
+                if (count>=_seen.size())
+                    return true;
+                boolean s=_seen.get(count);
+                _seen.set(count);
+                return s;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public DigestAuthenticator()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+        
+        String mna=configuration.getInitParameter("maxNonceAge");
+        if (mna!=null)
+        {
+            _maxNonceAgeMs=Long.valueOf(mna);
+        }
+    }
+
+   
+    /* ------------------------------------------------------------ */
+    public int getMaxNonceCount()
+    {
+        return _maxNC;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxNonceCount(int maxNC)
+    {
+        _maxNC = maxNC;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setMaxNonceAge(long maxNonceAgeInMillis)
+    {
+        _maxNonceAgeMs = maxNonceAgeInMillis;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getMaxNonceAge()
+    {
+        return _maxNonceAgeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getAuthMethod()
+    {
+        return Constraint.__DIGEST_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+    
+
+
+    /* ------------------------------------------------------------ */
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String credentials = request.getHeader(HttpHeaders.AUTHORIZATION);
+
+        try
+        {
+            boolean stale = false;
+            if (credentials != null)
+            {
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("Credentials: " + credentials);
+                QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(credentials, "=, ", true, false);
+                final Digest digest = new Digest(request.getMethod());
+                String last = null;
+                String name = null;
+
+                while (tokenizer.hasMoreTokens())
+                {
+                    String tok = tokenizer.nextToken();
+                    char c = (tok.length() == 1) ? tok.charAt(0) : '\0';
+
+                    switch (c)
+                    {
+                        case '=':
+                            name = last;
+                            last = tok;
+                            break;
+                        case ',':
+                            name = null;
+                            break;
+                        case ' ':
+                            break;
+
+                        default:
+                            last = tok;
+                            if (name != null)
+                            {
+                                if ("username".equalsIgnoreCase(name))
+                                    digest.username = tok;
+                                else if ("realm".equalsIgnoreCase(name))
+                                    digest.realm = tok;
+                                else if ("nonce".equalsIgnoreCase(name))
+                                    digest.nonce = tok;
+                                else if ("nc".equalsIgnoreCase(name))
+                                    digest.nc = tok;
+                                else if ("cnonce".equalsIgnoreCase(name))
+                                    digest.cnonce = tok;
+                                else if ("qop".equalsIgnoreCase(name))
+                                    digest.qop = tok;
+                                else if ("uri".equalsIgnoreCase(name))
+                                    digest.uri = tok;
+                                else if ("response".equalsIgnoreCase(name)) 
+                                    digest.response = tok;
+                                name=null;
+                            }
+                    }
+                }
+
+                int n = checkNonce(digest,(Request)request);
+
+                if (n > 0)
+                {
+                    //UserIdentity user = _loginService.login(digest.username,digest);
+                    UserIdentity user = login(digest.username, digest, req);
+                    if (user!=null)
+                    {
+                        return new UserAuthentication(getAuthMethod(),user);
+                    }
+                }
+                else if (n == 0) 
+                    stale = true;
+
+            }
+
+            if (!DeferredAuthentication.isDeferred(response))
+            {
+                String domain = request.getContextPath();
+                if (domain == null) 
+                    domain = "/";
+                response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"" + _loginService.getName()
+                        + "\", domain=\""
+                        + domain
+                        + "\", nonce=\""
+                        + newNonce((Request)request)
+                        + "\", algorithm=MD5, qop=\"auth\","
+                        + " stale=" + stale);
+                response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+
+                return Authentication.SEND_CONTINUE;
+            }
+
+            return Authentication.UNAUTHENTICATED;
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public String newNonce(Request request)
+    {
+        Nonce nonce;
+        
+        do
+        {
+            byte[] nounce = new byte[24];
+            _random.nextBytes(nounce);
+
+            nonce = new Nonce(new String(B64Code.encode(nounce)),request.getTimeStamp(),_maxNC);
+        }
+        while (_nonceMap.putIfAbsent(nonce._nonce,nonce)!=null);
+        _nonceQueue.add(nonce);
+               
+        return nonce._nonce;
+    }
+
+    /**
+     * @param nstring nonce to check
+     * @param request
+     * @return -1 for a bad nonce, 0 for a stale none, 1 for a good nonce
+     */
+    /* ------------------------------------------------------------ */
+    private int checkNonce(Digest digest, Request request)
+    {
+        // firstly let's expire old nonces
+        long expired = request.getTimeStamp()-_maxNonceAgeMs;
+        Nonce nonce=_nonceQueue.peek();
+        while (nonce!=null && nonce._ts<expired)
+        {
+            _nonceQueue.remove(nonce);
+            _nonceMap.remove(nonce._nonce);
+            nonce=_nonceQueue.peek();
+        }
+        
+        // Now check the requested nonce
+        try
+        {
+            nonce = _nonceMap.get(digest.nonce);
+            if (nonce==null)
+                return 0;
+         
+            long count = Long.parseLong(digest.nc,16);
+            if (count>=_maxNC)
+                return 0;
+            if (nonce.seen((int)count))
+                return -1;
+            return 1;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class Digest extends Credential
+    {
+        private static final long serialVersionUID = -2484639019549527724L;
+        final String method;
+        String username = "";
+        String realm = "";
+        String nonce = "";
+        String nc = "";
+        String cnonce = "";
+        String qop = "";
+        String uri = "";
+        String response = "";
+
+        /* ------------------------------------------------------------ */
+        Digest(String m)
+        {
+            method = m;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            String password = (credentials instanceof String) ? (String) credentials : credentials.toString();
+
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                byte[] ha1;
+                if (credentials instanceof Credential.MD5)
+                {
+                    // Credentials are already a MD5 digest - assume it's in
+                    // form user:realm:password (we have no way to know since
+                    // it's a digest, alright?)
+                    ha1 = ((Credential.MD5) credentials).getDigest();
+                }
+                else
+                {
+                    // calc A1 digest
+                    md.update(username.getBytes(StringUtil.__ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(realm.getBytes(StringUtil.__ISO_8859_1));
+                    md.update((byte) ':');
+                    md.update(password.getBytes(StringUtil.__ISO_8859_1));
+                    ha1 = md.digest();
+                }
+                // calc A2 digest
+                md.reset();
+                md.update(method.getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(uri.getBytes(StringUtil.__ISO_8859_1));
+                byte[] ha2 = md.digest();
+
+                // calc digest
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":"
+                // nc-value ":" unq(cnonce-value) ":" unq(qop-value) ":" H(A2) )
+                // <">
+                // request-digest = <"> < KD ( H(A1), unq(nonce-value) ":" H(A2)
+                // ) > <">
+
+                md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(nc.getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(qop.getBytes(StringUtil.__ISO_8859_1));
+                md.update((byte) ':');
+                md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
+                byte[] digest = md.digest();
+
+                // check digest
+                return (TypeUtil.toString(digest, 16).equalsIgnoreCase(response));
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return username + "," + response;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
new file mode 100644
index 0000000..abc74d0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -0,0 +1,502 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+/**
+ * FORM Authenticator.
+ * 
+ * <p>This authenticator implements form authentication will use dispatchers to
+ * the login page if the {@link #__FORM_DISPATCH} init parameter is set to true.
+ * Otherwise it will redirect.</p>
+ * 
+ * <p>The form authenticator redirects unauthenticated requests to a log page
+ * which should use a form to gather username/password from the user and send them
+ * to the /j_security_check URI within the context.  FormAuthentication uses 
+ * {@link SessionAuthentication} to wrap Authentication results so that they
+ * are  associated with the session.</p>
+ *  
+ * 
+ */
+public class FormAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(FormAuthenticator.class);
+
+    public final static String __FORM_LOGIN_PAGE="org.eclipse.jetty.security.form_login_page";
+    public final static String __FORM_ERROR_PAGE="org.eclipse.jetty.security.form_error_page";
+    public final static String __FORM_DISPATCH="org.eclipse.jetty.security.dispatch";
+    public final static String __J_URI = "org.eclipse.jetty.security.form_URI";
+    public final static String __J_POST = "org.eclipse.jetty.security.form_POST";
+    public final static String __J_SECURITY_CHECK = "/j_security_check";
+    public final static String __J_USERNAME = "j_username";
+    public final static String __J_PASSWORD = "j_password";
+
+    private String _formErrorPage;
+    private String _formErrorPath;
+    private String _formLoginPage;
+    private String _formLoginPath;
+    private boolean _dispatch;
+    private boolean _alwaysSaveUri;
+
+    public FormAuthenticator()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public FormAuthenticator(String login,String error,boolean dispatch)
+    {
+        this();
+        if (login!=null)
+            setLoginPage(login);
+        if (error!=null)
+            setErrorPage(error);
+        _dispatch=dispatch;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * If true, uris that cause a redirect to a login page will always
+     * be remembered. If false, only the first uri that leads to a login
+     * page redirect is remembered.
+     * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=379909
+     * @param alwaysSave
+     */
+    public void setAlwaysSaveUri (boolean alwaysSave)
+    {
+        _alwaysSaveUri = alwaysSave;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean getAlwaysSaveUri ()
+    {
+        return _alwaysSaveUri;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.security.authentication.LoginAuthenticator#setConfiguration(org.eclipse.jetty.security.Authenticator.AuthConfiguration)
+     */
+    @Override
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        super.setConfiguration(configuration);
+        String login=configuration.getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE);
+        if (login!=null)
+            setLoginPage(login);
+        String error=configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+        if (error!=null)
+            setErrorPage(error);
+        String dispatch=configuration.getInitParameter(FormAuthenticator.__FORM_DISPATCH);
+        _dispatch = dispatch==null?_dispatch:Boolean.valueOf(dispatch);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getAuthMethod()
+    {
+        return Constraint.__FORM_AUTH;
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setLoginPage(String path)
+    {
+        if (!path.startsWith("/"))
+        {
+            LOG.warn("form-login-page must start with /");
+            path = "/" + path;
+        }
+        _formLoginPage = path;
+        _formLoginPath = path;
+        if (_formLoginPath.indexOf('?') > 0) 
+            _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setErrorPage(String path)
+    {
+        if (path == null || path.trim().length() == 0)
+        {
+            _formErrorPath = null;
+            _formErrorPage = null;
+        }
+        else
+        {
+            if (!path.startsWith("/"))
+            {
+                LOG.warn("form-error-page must start with /");
+                path = "/" + path;
+            }
+            _formErrorPage = path;
+            _formErrorPath = path;
+
+            if (_formErrorPath.indexOf('?') > 0) 
+                _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        
+        UserIdentity user = super.login(username,password,request);
+        if (user!=null)
+        {
+            HttpSession session = ((HttpServletRequest)request).getSession(true);
+            Authentication cached=new SessionAuthentication(getAuthMethod(),user,password);
+            session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached);
+        }
+        return user;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException
+    {   
+        HttpServletRequest request = (HttpServletRequest)req;
+        HttpServletResponse response = (HttpServletResponse)res;
+        String uri = request.getRequestURI();
+        if (uri==null)
+            uri=URIUtil.SLASH;
+
+        mandatory|=isJSecurityCheck(uri);
+        if (!mandatory)
+            return new DeferredAuthentication(this);
+
+        if (isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo())) &&!DeferredAuthentication.isDeferred(response))
+            return new DeferredAuthentication(this);
+
+        HttpSession session = request.getSession(true);
+            
+        try
+        {
+            // Handle a request for authentication.
+            if (isJSecurityCheck(uri))
+            {
+                final String username = request.getParameter(__J_USERNAME);
+                final String password = request.getParameter(__J_PASSWORD);
+                
+                UserIdentity user = login(username, password, request);
+                session = request.getSession(true);
+                if (user!=null)
+                {                    
+                    // Redirect to original request
+                    String nuri;
+                    synchronized(session)
+                    {
+                        nuri = (String) session.getAttribute(__J_URI);
+
+                        if (nuri == null || nuri.length() == 0)
+                        {
+                            nuri = request.getContextPath();
+                            if (nuri.length() == 0) 
+                                nuri = URIUtil.SLASH;
+                        }
+                    }
+                    response.setContentLength(0);   
+                    response.sendRedirect(response.encodeRedirectURL(nuri));
+                    
+                    return new FormAuthentication(getAuthMethod(),user);
+                }
+                
+                // not authenticated
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
+                if (_formErrorPage == null)
+                {
+                    if (response != null) 
+                        response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                }
+                else if (_dispatch)
+                {
+                    RequestDispatcher dispatcher = request.getRequestDispatcher(_formErrorPage);
+                    response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
+                    response.setDateHeader(HttpHeaders.EXPIRES,1);
+                    dispatcher.forward(new FormRequest(request), new FormResponse(response));
+                }
+                else
+                {
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formErrorPage)));
+                }
+                
+                return Authentication.SEND_FAILURE;
+            }
+            
+            // Look for cached authentication
+            Authentication authentication = (Authentication) session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
+            if (authentication != null) 
+            {
+                // Has authentication been revoked?
+                if (authentication instanceof Authentication.User && 
+                    _loginService!=null &&
+                    !_loginService.validate(((Authentication.User)authentication).getUserIdentity()))
+                {
+                
+                    session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED);
+                }
+                else
+                {
+                    String j_uri=(String)session.getAttribute(__J_URI);
+                    if (j_uri!=null)
+                    {
+                        MultiMap<String> j_post = (MultiMap<String>)session.getAttribute(__J_POST);
+                        if (j_post!=null)
+                        {
+                            StringBuffer buf = request.getRequestURL();
+                            if (request.getQueryString() != null)
+                                buf.append("?").append(request.getQueryString());
+
+                            if (j_uri.equals(buf.toString()))
+                            {
+                                // This is a retry of an original POST request
+                                // so restore method and parameters
+
+                                session.removeAttribute(__J_POST);                        
+                                Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest();
+                                base_request.setMethod(HttpMethods.POST);
+                                base_request.setParameters(j_post);
+                            }
+                        }
+                        else
+                            session.removeAttribute(__J_URI);
+                            
+                    }
+                    return authentication;
+                }
+            }
+
+            // if we can't send challenge
+            if (DeferredAuthentication.isDeferred(response))
+            {
+                LOG.debug("auth deferred {}",session.getId());
+                return Authentication.UNAUTHENTICATED;
+            }
+
+            // remember the current URI
+            synchronized (session)
+            {
+                // But only if it is not set already, or we save every uri that leads to a login form redirect
+                if (session.getAttribute(__J_URI)==null || _alwaysSaveUri)
+                {  
+                    StringBuffer buf = request.getRequestURL();
+                    if (request.getQueryString() != null)
+                        buf.append("?").append(request.getQueryString());
+                    session.setAttribute(__J_URI, buf.toString());
+                    
+                    if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(req.getContentType()) && HttpMethods.POST.equals(request.getMethod()))
+                    {
+                        Request base_request = (req instanceof Request)?(Request)req:AbstractHttpConnection.getCurrentConnection().getRequest();
+                        base_request.extractParameters();                        
+                        session.setAttribute(__J_POST, new MultiMap<String>(base_request.getParameters()));
+                    }
+                }
+            }
+            
+            // send the the challenge
+            if (_dispatch)
+            {
+                RequestDispatcher dispatcher = request.getRequestDispatcher(_formLoginPage);
+                response.setHeader(HttpHeaders.CACHE_CONTROL,"No-cache");
+                response.setDateHeader(HttpHeaders.EXPIRES,1);
+                dispatcher.forward(new FormRequest(request), new FormResponse(response));
+            }
+            else
+            {
+                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(),_formLoginPage)));
+            }
+            return Authentication.SEND_CONTINUE;
+            
+         
+        }
+        catch (IOException e)
+        {
+            throw new ServerAuthException(e);
+        }
+        catch (ServletException e)
+        {
+            throw new ServerAuthException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isJSecurityCheck(String uri)
+    {
+        int jsc = uri.indexOf(__J_SECURITY_CHECK);
+        
+        if (jsc<0)
+            return false;
+        int e=jsc+__J_SECURITY_CHECK.length();
+        if (e==uri.length())
+            return true;
+        char c = uri.charAt(e);
+        return c==';'||c=='#'||c=='/'||c=='?';
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isLoginOrErrorPage(String pathInContext)
+    {
+        return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormRequest extends HttpServletRequestWrapper
+    {
+        public FormRequest(HttpServletRequest request)
+        {
+            super(request);
+        }
+
+        @Override
+        public long getDateHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return -1;
+            return super.getDateHeader(name);
+        }
+        
+        @Override
+        public String getHeader(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return null;
+            return super.getHeader(name);
+        }
+
+        @Override
+        public Enumeration getHeaderNames()
+        {
+            return Collections.enumeration(Collections.list(super.getHeaderNames()));
+        }
+
+        @Override
+        public Enumeration getHeaders(String name)
+        {
+            if (name.toLowerCase(Locale.ENGLISH).startsWith("if-"))
+                return Collections.enumeration(Collections.EMPTY_LIST);
+            return super.getHeaders(name);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected static class FormResponse extends HttpServletResponseWrapper
+    {
+        public FormResponse(HttpServletResponse response)
+        {
+            super(response);
+        }
+
+        @Override
+        public void addDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.addDateHeader(name,date);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.addHeader(name,value);
+        }
+
+        @Override
+        public void setDateHeader(String name, long date)
+        {
+            if (notIgnored(name))
+                super.setDateHeader(name,date);
+        }
+        
+        @Override
+        public void setHeader(String name, String value)
+        {
+            if (notIgnored(name))
+                super.setHeader(name,value);
+        }
+        
+        private boolean notIgnored(String name)
+        {
+            if (HttpHeaders.CACHE_CONTROL.equalsIgnoreCase(name) ||
+                HttpHeaders.PRAGMA.equalsIgnoreCase(name) ||
+                HttpHeaders.ETAG.equalsIgnoreCase(name) ||
+                HttpHeaders.EXPIRES.equalsIgnoreCase(name) ||
+                HttpHeaders.LAST_MODIFIED.equalsIgnoreCase(name) ||
+                HttpHeaders.AGE.equalsIgnoreCase(name))
+                return false;
+            return true;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** This Authentication represents a just completed Form authentication.
+     * Subsequent requests from the same user are authenticated by the presents 
+     * of a {@link SessionAuthentication} instance in their session.
+     */
+    public static class FormAuthentication extends UserAuthentication implements Authentication.ResponseSent
+    {
+        public FormAuthentication(String method, UserIdentity userIdentity)
+        {
+            super(method,userIdentity);
+        }
+        
+        @Override
+        public String toString()
+        {
+            return "Form"+super.toString();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
new file mode 100644
index 0000000..442bb6a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
@@ -0,0 +1,98 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.security.Authenticator;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+
+public abstract class LoginAuthenticator implements Authenticator
+{
+    protected LoginService _loginService;
+    protected IdentityService _identityService;
+    private boolean _renewSession;
+
+    protected LoginAuthenticator()
+    {
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity login(String username, Object password, ServletRequest request)
+    {
+        UserIdentity user = _loginService.login(username,password);
+        if (user!=null)
+        {
+            renewSession((HttpServletRequest)request, null);
+            return user;
+        }
+        return null;
+    }
+
+
+    public void setConfiguration(AuthConfiguration configuration)
+    {
+        _loginService=configuration.getLoginService();
+        if (_loginService==null)
+            throw new IllegalStateException("No LoginService for "+this+" in "+configuration);
+        _identityService=configuration.getIdentityService();
+        if (_identityService==null)
+            throw new IllegalStateException("No IdentityService for "+this+" in "+configuration);
+        _renewSession=configuration.isSessionRenewedOnAuthentication();
+    }
+    
+    public LoginService getLoginService()
+    {
+        return _loginService;
+    }
+    
+    /** Change the session id.
+     * The session is changed to a new instance with a new ID if and only if:<ul>
+     * <li>A session exists.
+     * <li>The {@link AuthConfiguration#isSessionRenewedOnAuthentication()} returns true.
+     * <li>The session ID has been given to unauthenticated responses
+     * </ul>
+     * @param request
+     * @param response
+     * @return The new session.
+     */
+    protected HttpSession renewSession(HttpServletRequest request, HttpServletResponse response)
+    {
+        HttpSession httpSession = request.getSession(false);
+       
+        //if we should renew sessions, and there is an existing session that may have been seen by non-authenticated users
+        //(indicated by SESSION_SECURED not being set on the session) then we should change id
+        if (_renewSession && httpSession!=null && httpSession.getAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED)!=Boolean.TRUE)
+        {
+            synchronized (this)
+            {
+                httpSession = AbstractSessionManager.renewSession(request, httpSession,true);
+            }
+        }
+        return httpSession;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/LoginCallback.java b/src/java/org/eclipse/jetty/security/authentication/LoginCallback.java
new file mode 100644
index 0000000..0d56e01
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/LoginCallback.java
@@ -0,0 +1,55 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ * 
+ * @version $Rev: 4792 $ $Date: 2009-03-18 22:55:52 +0100 (Wed, 18 Mar 2009) $
+ */
+public interface LoginCallback
+{
+    public Subject getSubject();
+
+    public String getUserName();
+
+    public Object getCredential();
+ 
+    public boolean isSuccess();
+
+    public void setSuccess(boolean success);
+
+    public Principal getUserPrincipal();
+
+    public void setUserPrincipal(Principal userPrincipal);
+
+    public String[] getRoles();
+    
+    public void setRoles(String[] roles);
+  
+    public void clearPassword();
+   
+
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java b/src/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
new file mode 100644
index 0000000..c0cd219
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/LoginCallbackImpl.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.security.Principal;
+
+import javax.security.auth.Subject;
+
+import org.eclipse.jetty.security.IdentityService;
+
+/**
+ * This is similar to the jaspi PasswordValidationCallback but includes user
+ * principal and group info as well.
+ * 
+ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
+ */
+public class LoginCallbackImpl implements LoginCallback
+{
+    // initial data
+    private final Subject subject;
+
+    private final String userName;
+
+    private Object credential;
+
+    private boolean success;
+
+    private Principal userPrincipal;
+
+    private String[] roles = IdentityService.NO_ROLES;
+
+    //TODO could use Credential instance instead of Object if Basic/Form create a Password object
+    public LoginCallbackImpl (Subject subject, String userName, Object credential)
+    {
+        this.subject = subject;
+        this.userName = userName;
+        this.credential = credential;
+    }
+
+    public Subject getSubject()
+    {
+        return subject;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public Object getCredential()
+    {
+        return credential;
+    }
+
+    public boolean isSuccess()
+    {
+        return success;
+    }
+
+    public void setSuccess(boolean success)
+    {
+        this.success = success;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return userPrincipal;
+    }
+
+    public void setUserPrincipal(Principal userPrincipal)
+    {
+        this.userPrincipal = userPrincipal;
+    }
+
+    public String[] getRoles()
+    {
+        return roles;
+    }
+
+    public void setRoles(String[] groups)
+    {
+        this.roles = groups;
+    }
+
+    public void clearPassword()
+    {
+        if (credential != null)
+        {
+            credential = null;
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/src/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
new file mode 100644
index 0000000..0f2db6d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.UserIdentity.Scope;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SessionAuthentication implements Authentication.User, Serializable, HttpSessionActivationListener, HttpSessionBindingListener
+{
+    private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
+
+    private static final long serialVersionUID = -4643200685888258706L;
+
+    
+
+    public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
+
+    private final String _method;
+    private final String _name;
+    private final Object _credentials;
+    
+    private transient UserIdentity _userIdentity;
+    private transient HttpSession _session;
+    
+    public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
+    {
+        _method = method;
+        _userIdentity = userIdentity;
+        _name=_userIdentity.getUserPrincipal().getName();
+        _credentials=credentials;
+    }
+
+    public String getAuthMethod()
+    {
+        return _method;
+    }
+
+    public UserIdentity getUserIdentity()
+    {
+        return _userIdentity;
+    }
+
+    public boolean isUserInRole(Scope scope, String role)
+    {
+        return _userIdentity.isUserInRole(role, scope);
+    }
+
+    private void readObject(ObjectInputStream stream) 
+        throws IOException, ClassNotFoundException 
+    {
+        stream.defaultReadObject();
+        
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security==null)
+            throw new IllegalStateException("!SecurityHandler");
+        LoginService login_service=security.getLoginService();
+        if (login_service==null)
+            throw new IllegalStateException("!LoginService");
+        
+        _userIdentity=login_service.login(_name,_credentials);
+        LOG.debug("Deserialized and relogged in {}",this);
+    }
+    
+    public void logout()
+    {
+        if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null)
+            _session.removeAttribute(__J_AUTHENTICATED);
+
+        doLogout();
+    }
+    
+    private void doLogout()
+    {
+        SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
+        if (security!=null)
+            security.logout(this);
+        if (_session!=null)
+            _session.removeAttribute(AbstractSessionManager.SESSION_KNOWN_ONLY_TO_AUTHENTICATED);
+    }
+        
+    @Override
+    public String toString()
+    {
+        return "Session"+super.toString();
+    }
+
+    public void sessionWillPassivate(HttpSessionEvent se)
+    {
+       
+    }
+
+    public void sessionDidActivate(HttpSessionEvent se)
+    {
+        if (_session==null)
+        {
+            _session=se.getSession();
+        }
+    }
+
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        if (_session==null)
+        {
+            _session=event.getSession();
+        }
+    }
+
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+        doLogout();
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java b/src/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
new file mode 100644
index 0000000..7d933bd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/security/authentication/SpnegoAuthenticator.java
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.security.authentication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Authentication.User;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.security.Constraint;
+
+public class SpnegoAuthenticator extends LoginAuthenticator
+{
+    private static final Logger LOG = Log.getLogger(SpnegoAuthenticator.class);
+    
+    private String _authMethod = Constraint.__SPNEGO_AUTH;
+    
+    public SpnegoAuthenticator()
+    {
+    	
+    }
+    
+    /**
+     * Allow for a custom authMethod value to be set for instances where SPENGO may not be appropriate
+     * @param authMethod
+     */
+    public SpnegoAuthenticator( String authMethod )
+    {
+    	_authMethod = authMethod;
+    }
+    
+    public String getAuthMethod()
+    {
+        return _authMethod;
+    }
+
+
+
+    public Authentication validateRequest(ServletRequest request, ServletResponse response, boolean mandatory) throws ServerAuthException
+    {        
+        HttpServletRequest req = (HttpServletRequest)request;
+        HttpServletResponse res = (HttpServletResponse)response;
+        
+        String header = req.getHeader(HttpHeaders.AUTHORIZATION);
+
+        if (!mandatory)
+        {
+            return new DeferredAuthentication(this);
+        }
+        
+        // check to see if we have authorization headers required to continue
+        if ( header == null )
+        {
+            try
+            {
+            	 if (DeferredAuthentication.isDeferred(res))
+            	 {
+                     return Authentication.UNAUTHENTICATED;
+            	 }
+            	 
+                LOG.debug("SpengoAuthenticator: sending challenge");
+                res.setHeader(HttpHeaders.WWW_AUTHENTICATE, HttpHeaders.NEGOTIATE);
+                res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+                return Authentication.SEND_CONTINUE;
+            } 
+            catch (IOException ioe)
+            {
+                throw new ServerAuthException(ioe);
+            }       
+        }
+        else if (header != null && header.startsWith(HttpHeaders.NEGOTIATE))
+        {
+            String spnegoToken = header.substring(10);
+            
+            UserIdentity user = login(null,spnegoToken, request);
+            
+            if ( user != null )
+            {
+                return new UserAuthentication(getAuthMethod(),user);
+            }
+        }
+        
+        return Authentication.UNAUTHENTICATED;
+    }
+
+    public boolean secureResponse(ServletRequest request, ServletResponse response, boolean mandatory, User validatedUser) throws ServerAuthException
+    {
+        return true;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/AbstractConnector.java b/src/java/org/eclipse/jetty/server/AbstractConnector.java
new file mode 100644
index 0000000..2226242
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -0,0 +1,1222 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.servlet.ServletRequest;
+
+import org.eclipse.jetty.http.HttpBuffers;
+import org.eclipse.jetty.http.HttpBuffersImpl;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/**
+ * Abstract Connector implementation. This abstract implementation of the Connector interface provides:
+ * <ul>
+ * <li>AbstractLifeCycle implementation</li>
+ * <li>Implementations for connector getters and setters</li>
+ * <li>Buffer management</li>
+ * <li>Socket configuration</li>
+ * <li>Base acceptor thread</li>
+ * <li>Optional reverse proxy headers checking</li>
+ * </ul>
+ */
+public abstract class AbstractConnector extends AggregateLifeCycle implements HttpBuffers, Connector, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(AbstractConnector.class);
+
+    private String _name;
+
+    private Server _server;
+    private ThreadPool _threadPool;
+    private String _host;
+    private int _port = 0;
+    private String _integralScheme = HttpSchemes.HTTPS;
+    private int _integralPort = 0;
+    private String _confidentialScheme = HttpSchemes.HTTPS;
+    private int _confidentialPort = 0;
+    private int _acceptQueueSize = 0;
+    private int _acceptors = 1;
+    private int _acceptorPriorityOffset = 0;
+    private boolean _useDNS;
+    private boolean _forwarded;
+    private String _hostHeader;
+
+    private String _forwardedHostHeader = HttpHeaders.X_FORWARDED_HOST;
+    private String _forwardedServerHeader = HttpHeaders.X_FORWARDED_SERVER;
+    private String _forwardedForHeader = HttpHeaders.X_FORWARDED_FOR;
+    private String _forwardedProtoHeader = HttpHeaders.X_FORWARDED_PROTO;
+    private String _forwardedCipherSuiteHeader;
+    private String _forwardedSslSessionIdHeader;
+    private boolean _reuseAddress = true;
+
+    protected int _maxIdleTime = 200000;
+    protected int _lowResourceMaxIdleTime = -1;
+    protected int _soLingerTime = -1;
+
+    private transient Thread[] _acceptorThreads;
+
+    private final AtomicLong _statsStartedAt = new AtomicLong(-1L);
+
+    /** connections to server */
+    private final CounterStatistic _connectionStats = new CounterStatistic();
+    /** requests per connection */
+    private final SampleStatistic _requestStats = new SampleStatistic();
+    /** duration of a connection */
+    private final SampleStatistic _connectionDurationStats = new SampleStatistic();
+
+    protected final HttpBuffersImpl _buffers = new HttpBuffersImpl();
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public AbstractConnector()
+    {
+        addBean(_buffers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setServer(Server server)
+    {
+        _server = server;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the ThreadPool.
+     * The threadpool passed is added via {@link #addBean(Object)} so that 
+     * it's lifecycle may be managed as a {@link AggregateLifeCycle}.
+     * @param pool the threadPool to set
+     */
+    public void setThreadPool(ThreadPool pool)
+    {
+        removeBean(_threadPool);
+        _threadPool = pool;
+        addBean(_threadPool);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setHost(String host)
+    {
+        _host = host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String getHost()
+    {
+        return _host;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setPort(int port)
+    {
+        _port = port;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getPort()
+    {
+        return _port;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxIdleTime.
+     */
+    public int getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum Idle time for a connection, which roughly translates to the {@link Socket#setSoTimeout(int)} call, although with NIO implementations
+     * other mechanisms may be used to implement the timeout. The max idle time is applied:
+     * <ul>
+     * <li>When waiting for a new request to be received on a connection</li>
+     * <li>When reading the headers and content of a request</li>
+     * <li>When writing the headers and content of a response</li>
+     * </ul>
+     * Jetty interprets this value as the maximum time between some progress being made on the connection. So if a single byte is read or written, then the
+     * timeout (if implemented by jetty) is reset. However, in many instances, the reading/writing is delegated to the JVM, and the semantic is more strictly
+     * enforced as the maximum time a single read/write operation can take. Note, that as Jetty supports writes of memory mapped file buffers, then a write may
+     * take many 10s of seconds for large content written to a slow device.
+     * <p>
+     * Previously, Jetty supported separate idle timeouts and IO operation timeouts, however the expense of changing the value of soTimeout was significant, so
+     * these timeouts were merged. With the advent of NIO, it may be possible to again differentiate these values (if there is demand).
+     *
+     * @param maxIdleTime
+     *            The maxIdleTime to set.
+     */
+    public void setMaxIdleTime(int maxIdleTime)
+    {
+        _maxIdleTime = maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxIdleTime when resources are low.
+     */
+    public int getLowResourcesMaxIdleTime()
+    {
+        return _lowResourceMaxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxIdleTime
+     *            The maxIdleTime to set when resources are low.
+     */
+    public void setLowResourcesMaxIdleTime(int maxIdleTime)
+    {
+        _lowResourceMaxIdleTime = maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxIdleTime when resources are low.
+     * @deprecated
+     */
+    @Deprecated
+    public final int getLowResourceMaxIdleTime()
+    {
+        return getLowResourcesMaxIdleTime();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxIdleTime
+     *            The maxIdleTime to set when resources are low.
+     * @deprecated
+     */
+    @Deprecated
+    public final void setLowResourceMaxIdleTime(int maxIdleTime)
+    {
+        setLowResourcesMaxIdleTime(maxIdleTime);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the soLingerTime.
+     */
+    public int getSoLingerTime()
+    {
+        return _soLingerTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the acceptQueueSize.
+     */
+    public int getAcceptQueueSize()
+    {
+        return _acceptQueueSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param acceptQueueSize
+     *            The acceptQueueSize to set.
+     */
+    public void setAcceptQueueSize(int acceptQueueSize)
+    {
+        _acceptQueueSize = acceptQueueSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the number of acceptor threads.
+     */
+    public int getAcceptors()
+    {
+        return _acceptors;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param acceptors
+     *            The number of acceptor threads to set.
+     */
+    public void setAcceptors(int acceptors)
+    {
+        if (acceptors > 2 * Runtime.getRuntime().availableProcessors())
+            LOG.warn("Acceptors should be <=2*availableProcessors: " + this);
+        _acceptors = acceptors;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param soLingerTime
+     *            The soLingerTime to set or -1 to disable.
+     */
+    public void setSoLingerTime(int soLingerTime)
+    {
+        _soLingerTime = soLingerTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_server == null)
+            throw new IllegalStateException("No server");
+
+        // open listener port
+        open();
+
+        if (_threadPool == null)
+        {
+            _threadPool = _server.getThreadPool();
+            addBean(_threadPool,false);
+        }
+
+        super.doStart();
+
+        // Start selector thread
+        synchronized (this)
+        {
+            _acceptorThreads = new Thread[getAcceptors()];
+
+            for (int i = 0; i < _acceptorThreads.length; i++)
+                if (!_threadPool.dispatch(new Acceptor(i)))
+                    throw new IllegalStateException("!accepting");
+            if (_threadPool.isLowOnThreads())
+                LOG.warn("insufficient threads configured for {}",this);
+        }
+
+        LOG.info("Started {}",this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        try
+        {
+            close();
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+
+        super.doStop();
+
+        Thread[] acceptors;
+        synchronized (this)
+        {
+            acceptors = _acceptorThreads;
+            _acceptorThreads = null;
+        }
+        if (acceptors != null)
+        {
+            for (Thread thread : acceptors)
+            {
+                if (thread != null)
+                    thread.interrupt();
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        Thread[] threads;
+        synchronized(this)
+        {
+            threads=_acceptorThreads;
+        }
+        if (threads != null)
+            for (Thread thread : threads)
+                if (thread != null)
+                    thread.join();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void configure(Socket socket) throws IOException
+    {
+        try
+        {
+            socket.setTcpNoDelay(true);
+            if (_soLingerTime >= 0)
+                socket.setSoLinger(true,_soLingerTime / 1000);
+            else
+                socket.setSoLinger(false,0);
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void customize(EndPoint endpoint, Request request) throws IOException
+    {
+        if (isForwarded())
+            checkForwardedHeaders(endpoint,request);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void checkForwardedHeaders(EndPoint endpoint, Request request) throws IOException
+    {
+        HttpFields httpFields = request.getConnection().getRequestFields();
+
+        // Do SSL first
+        if (getForwardedCipherSuiteHeader()!=null)
+        {
+            String cipher_suite=httpFields.getStringField(getForwardedCipherSuiteHeader());
+            if (cipher_suite!=null)
+                request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
+        }
+        if (getForwardedSslSessionIdHeader()!=null)
+        {
+            String ssl_session_id=httpFields.getStringField(getForwardedSslSessionIdHeader());
+            if(ssl_session_id!=null)
+            {
+                request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
+                request.setScheme(HttpSchemes.HTTPS);
+            }
+        }
+
+        // Retrieving headers from the request
+        String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
+        String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
+        String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
+        String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
+
+        if (_hostHeader != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeaders.HOST_BUFFER,_hostHeader);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedHost != null)
+        {
+            // Update host header
+            httpFields.put(HttpHeaders.HOST_BUFFER,forwardedHost);
+            request.setServerName(null);
+            request.setServerPort(-1);
+            request.getServerName();
+        }
+        else if (forwardedServer != null)
+        {
+            // Use provided server name
+            request.setServerName(forwardedServer);
+        }
+
+        if (forwardedFor != null)
+        {
+            request.setRemoteAddr(forwardedFor);
+            InetAddress inetAddress = null;
+
+            if (_useDNS)
+            {
+                try
+                {
+                    inetAddress = InetAddress.getByName(forwardedFor);
+                }
+                catch (UnknownHostException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+
+            request.setRemoteHost(inetAddress == null?forwardedFor:inetAddress.getHostName());
+        }
+
+        if (forwardedProto != null)
+        {
+            request.setScheme(forwardedProto);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String getLeftMostFieldValue(HttpFields fields, String header)
+    {
+        if (header == null)
+            return null;
+
+        String headerValue = fields.getStringField(header);
+
+        if (headerValue == null)
+            return null;
+
+        int commaIndex = headerValue.indexOf(',');
+
+        if (commaIndex == -1)
+        {
+            // Single value
+            return headerValue;
+        }
+
+        // The left-most value is the farthest downstream client
+        return headerValue.substring(0,commaIndex);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void persist(EndPoint endpoint) throws IOException
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
+     */
+    public int getConfidentialPort()
+    {
+        return _confidentialPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#getConfidentialScheme()
+     */
+    public String getConfidentialScheme()
+    {
+        return _confidentialScheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server .Request)
+     */
+    public boolean isIntegral(Request request)
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#getConfidentialPort()
+     */
+    public int getIntegralPort()
+    {
+        return _integralPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#getIntegralScheme()
+     */
+    public String getIntegralScheme()
+    {
+        return _integralScheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Connector#isConfidential(org.eclipse.jetty.server.Request)
+     */
+    public boolean isConfidential(Request request)
+    {
+        return _forwarded && request.getScheme().equalsIgnoreCase(HttpSchemes.HTTPS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param confidentialPort
+     *            The confidentialPort to set.
+     */
+    public void setConfidentialPort(int confidentialPort)
+    {
+        _confidentialPort = confidentialPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param confidentialScheme
+     *            The confidentialScheme to set.
+     */
+    public void setConfidentialScheme(String confidentialScheme)
+    {
+        _confidentialScheme = confidentialScheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param integralPort
+     *            The integralPort to set.
+     */
+    public void setIntegralPort(int integralPort)
+    {
+        _integralPort = integralPort;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param integralScheme
+     *            The integralScheme to set.
+     */
+    public void setIntegralScheme(String integralScheme)
+    {
+        _integralScheme = integralScheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract void accept(int acceptorID) throws IOException, InterruptedException;
+
+    /* ------------------------------------------------------------ */
+    public void stopAccept(int acceptorID) throws Exception
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getResolveNames()
+    {
+        return _useDNS;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setResolveNames(boolean resolve)
+    {
+        _useDNS = resolve;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Is reverse proxy handling on?
+     *
+     * @return true if this connector is checking the x-forwarded-for/host/server headers
+     */
+    public boolean isForwarded()
+    {
+        return _forwarded;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set reverse proxy handling. If set to true, then the X-Forwarded headers (or the headers set in their place) are looked for to set the request protocol,
+     * host, server and client ip.
+     *
+     * @param check
+     *            true if this connector is checking the x-forwarded-for/host/server headers
+     * @see #setForwardedForHeader(String)
+     * @see #setForwardedHostHeader(String)
+     * @see #setForwardedProtoHeader(String)
+     * @see #setForwardedServerHeader(String)
+     */
+    public void setForwarded(boolean check)
+    {
+        if (check)
+            LOG.debug("{} is forwarded",this);
+        _forwarded = check;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
+     * This value is only used if {@link #isForwarded()} is true.
+     *
+     * @param hostHeader
+     *            The value of the host header to force.
+     */
+    public void setHostHeader(String hostHeader)
+    {
+        _hostHeader = hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     *
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedHostHeader()
+    {
+        return _forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedHostHeader
+     *            The header name for forwarded hosts (default x-forwarded-host)
+     * @see #setForwarded(boolean)
+     */
+    public void setForwardedHostHeader(String forwardedHostHeader)
+    {
+        _forwardedHostHeader = forwardedHostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the header name for forwarded server.
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedServerHeader()
+    {
+        return _forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedServerHeader
+     *            The header name for forwarded server (default x-forwarded-server)
+     * @see #setForwarded(boolean)
+     */
+    public void setForwardedServerHeader(String forwardedServerHeader)
+    {
+        _forwardedServerHeader = forwardedServerHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedForHeader()
+    {
+        return _forwardedForHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedRemoteAddressHeader
+     *            The header name for forwarded for (default x-forwarded-for)
+     * @see #setForwarded(boolean)
+     */
+    public void setForwardedForHeader(String forwardedRemoteAddressHeader)
+    {
+        _forwardedForHeader = forwardedRemoteAddressHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the forwardedProtoHeader.
+     *
+     * @return the forwardedProtoHeader (default X-Forwarded-For)
+     * @see #setForwarded(boolean)
+     */
+    public String getForwardedProtoHeader()
+    {
+        return _forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the forwardedProtoHeader.
+     *
+     * @param forwardedProtoHeader
+     *            the forwardedProtoHeader to set (default X-Forwarded-For)
+     * @see #setForwarded(boolean)
+     */
+    public void setForwardedProtoHeader(String forwardedProtoHeader)
+    {
+        _forwardedProtoHeader = forwardedProtoHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded cipher suite (default null)
+     */
+    public String getForwardedCipherSuiteHeader()
+    {
+        return _forwardedCipherSuiteHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedCipherSuite
+     *            The header name holding a forwarded cipher suite (default null)
+     */
+    public void setForwardedCipherSuiteHeader(String forwardedCipherSuite)
+    {
+        _forwardedCipherSuiteHeader = forwardedCipherSuite;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The header name holding a forwarded SSL Session ID (default null)
+     */
+    public String getForwardedSslSessionIdHeader()
+    {
+        return _forwardedSslSessionIdHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forwardedSslSessionId
+     *            The header name holding a forwarded SSL Session ID (default null)
+     */
+    public void setForwardedSslSessionIdHeader(String forwardedSslSessionId)
+    {
+        _forwardedSslSessionIdHeader = forwardedSslSessionId;
+    }
+
+    public int getRequestBufferSize()
+    {
+        return _buffers.getRequestBufferSize();
+    }
+
+    public void setRequestBufferSize(int requestBufferSize)
+    {
+        _buffers.setRequestBufferSize(requestBufferSize);
+    }
+
+    public int getRequestHeaderSize()
+    {
+        return _buffers.getRequestHeaderSize();
+    }
+
+    public void setRequestHeaderSize(int requestHeaderSize)
+    {
+        _buffers.setRequestHeaderSize(requestHeaderSize);
+    }
+
+    public int getResponseBufferSize()
+    {
+        return _buffers.getResponseBufferSize();
+    }
+
+    public void setResponseBufferSize(int responseBufferSize)
+    {
+        _buffers.setResponseBufferSize(responseBufferSize);
+    }
+
+    public int getResponseHeaderSize()
+    {
+        return _buffers.getResponseHeaderSize();
+    }
+
+    public void setResponseHeaderSize(int responseHeaderSize)
+    {
+        _buffers.setResponseHeaderSize(responseHeaderSize);
+    }
+
+    public Type getRequestBufferType()
+    {
+        return _buffers.getRequestBufferType();
+    }
+
+    public Type getRequestHeaderType()
+    {
+        return _buffers.getRequestHeaderType();
+    }
+
+    public Type getResponseBufferType()
+    {
+        return _buffers.getResponseBufferType();
+    }
+
+    public Type getResponseHeaderType()
+    {
+        return _buffers.getResponseHeaderType();
+    }
+
+    public void setRequestBuffers(Buffers requestBuffers)
+    {
+        _buffers.setRequestBuffers(requestBuffers);
+    }
+
+    public void setResponseBuffers(Buffers responseBuffers)
+    {
+        _buffers.setResponseBuffers(responseBuffers);
+    }
+
+    public Buffers getRequestBuffers()
+    {
+        return _buffers.getRequestBuffers();
+    }
+
+    public Buffers getResponseBuffers()
+    {
+        return _buffers.getResponseBuffers();
+    }
+
+    public void setMaxBuffers(int maxBuffers)
+    {
+        _buffers.setMaxBuffers(maxBuffers);
+    }
+
+    public int getMaxBuffers()
+    {
+        return _buffers.getMaxBuffers();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%s:%d",
+                getClass().getSimpleName(),
+                getHost()==null?"0.0.0.0":getHost(),
+                getLocalPort()<=0?getPort():getLocalPort());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class Acceptor implements Runnable
+    {
+        int _acceptor = 0;
+
+        Acceptor(int id)
+        {
+            _acceptor = id;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void run()
+        {
+            Thread current = Thread.currentThread();
+            String name;
+            synchronized (AbstractConnector.this)
+            {
+                if (_acceptorThreads == null)
+                    return;
+
+                _acceptorThreads[_acceptor] = current;
+                name = _acceptorThreads[_acceptor].getName();
+                current.setName(name + " Acceptor" + _acceptor + " " + AbstractConnector.this);
+            }
+            int old_priority = current.getPriority();
+
+            try
+            {
+                current.setPriority(old_priority - _acceptorPriorityOffset);
+                while (isRunning() && getConnection() != null)
+                {
+                    try
+                    {
+                        accept(_acceptor);
+                    }
+                    catch (EofException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (IOException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (InterruptedException x)
+                    {
+                        // Connector has been stopped
+                        LOG.ignore(x);
+                    }
+                    catch (Throwable e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+            finally
+            {
+                current.setPriority(old_priority);
+                current.setName(name);
+
+                synchronized (AbstractConnector.this)
+                {
+                    if (_acceptorThreads != null)
+                        _acceptorThreads[_acceptor] = null;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getName()
+    {
+        if (_name == null)
+            _name = (getHost() == null?"0.0.0.0":getHost()) + ":" + (getLocalPort() <= 0?getPort():getLocalPort());
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Get the number of requests handled by this connector since last call of statsReset(). If setStatsOn(false) then this is undefined.
+     */
+    public int getRequests()
+    {
+        return (int)_requestStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connectionsDurationTotal.
+     */
+    public long getConnectionsDurationTotal()
+    {
+        return _connectionDurationStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnections()
+    {
+        return (int)_connectionStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsOpen()
+    {
+        return (int)_connectionStats.getCurrent();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsOpenMax()
+    {
+        return (int)_connectionStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Mean duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsDurationMean()
+    {
+        return _connectionDurationStats.getMean();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public long getConnectionsDurationMax()
+    {
+        return _connectionDurationStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Standard deviation of duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsDurationStdDev()
+    {
+        return _connectionDurationStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Mean number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsRequestsMean()
+    {
+        return _requestStats.getMean();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsRequestsMax()
+    {
+        return (int)_requestStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Standard deviation of number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsRequestsStdDev()
+    {
+        return _requestStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset statistics.
+     */
+    public void statsReset()
+    {
+        updateNotEqual(_statsStartedAt,-1,System.currentTimeMillis());
+
+        _requestStats.reset();
+        _connectionStats.reset();
+        _connectionDurationStats.reset();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStatsOn(boolean on)
+    {
+        if (on && _statsStartedAt.get() != -1)
+            return;
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("Statistics on = " + on + " for " + this);
+
+        statsReset();
+        _statsStartedAt.set(on?System.currentTimeMillis():-1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if statistics collection is turned on.
+     */
+    public boolean getStatsOn()
+    {
+        return _statsStartedAt.get() != -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Timestamp stats were started at.
+     */
+    public long getStatsOnMs()
+    {
+        long start = _statsStartedAt.get();
+
+        return (start != -1)?(System.currentTimeMillis() - start):0;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void connectionOpened(Connection connection)
+    {
+        if (_statsStartedAt.get() == -1)
+            return;
+
+        _connectionStats.increment();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void connectionUpgraded(Connection oldConnection, Connection newConnection)
+    {
+        _requestStats.set((oldConnection instanceof AbstractHttpConnection)?((AbstractHttpConnection)oldConnection).getRequests():0);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void connectionClosed(Connection connection)
+    {
+        connection.onClose();
+
+        if (_statsStartedAt.get() == -1)
+            return;
+
+        long duration = System.currentTimeMillis() - connection.getTimeStamp();
+        int requests = (connection instanceof AbstractHttpConnection)?((AbstractHttpConnection)connection).getRequests():0;
+        _requestStats.set(requests);
+        _connectionStats.decrement();
+        _connectionDurationStats.set(duration);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the acceptorPriority
+     */
+    public int getAcceptorPriorityOffset()
+    {
+        return _acceptorPriorityOffset;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the priority offset of the acceptor threads. The priority is adjusted by this amount (default 0) to either favour the acceptance of new threads and
+     * newly active connections or to favour the handling of already dispatched connections.
+     *
+     * @param offset
+     *            the amount to alter the priority of the acceptor threads.
+     */
+    public void setAcceptorPriorityOffset(int offset)
+    {
+        _acceptorPriorityOffset = offset;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the the server socket will be opened in SO_REUSEADDR mode.
+     */
+    public boolean getReuseAddress()
+    {
+        return _reuseAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param reuseAddress
+     *            True if the the server socket will be opened in SO_REUSEADDR mode.
+     */
+    public void setReuseAddress(boolean reuseAddress)
+    {
+        _reuseAddress = reuseAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLowResources()
+    {
+        if (_threadPool != null)
+            return _threadPool.isLowOnThreads();
+        return _server.getThreadPool().isLowOnThreads();
+    }
+
+    /* ------------------------------------------------------------ */
+    private void updateNotEqual(AtomicLong valueHolder, long compare, long value)
+    {
+        long oldValue = valueHolder.get();
+        while (compare != oldValue)
+        {
+            if (valueHolder.compareAndSet(oldValue,value))
+                break;
+            oldValue = valueHolder.get();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/AbstractHttpConnection.java b/src/java/org/eclipse/jetty/server/AbstractHttpConnection.java
new file mode 100644
index 0000000..4cdc132
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/AbstractHttpConnection.java
@@ -0,0 +1,1273 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.continuation.ContinuationThrowable;
+import org.eclipse.jetty.http.EncodedHttpURI;
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.http.HttpBuffers;
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.Parser;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.UncheckedPrintWriter;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.nio.NIOConnector;
+import org.eclipse.jetty.server.ssl.SslConnector;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * <p>A HttpConnection represents the connection of a HTTP client to the server
+ * and is created by an instance of a {@link Connector}. It's prime function is
+ * to associate {@link Request} and {@link Response} instances with a {@link EndPoint}.
+ * </p>
+ * <p>
+ * A connection is also the prime mechanism used by jetty to recycle objects without
+ * pooling.  The {@link Request},  {@link Response}, {@link HttpParser}, {@link HttpGenerator}
+ * and {@link HttpFields} instances are all recycled for the duraction of
+ * a connection. Where appropriate, allocated buffers are also kept associated
+ * with the connection via the parser and/or generator.
+ * </p>
+ * <p>
+ * The connection state is held by 3 separate state machines: The request state, the
+ * response state and the continuation state.  All three state machines must be driven
+ * to completion for every request, and all three can complete in any order.
+ * </p>
+ * <p>
+ * The HttpConnection support protocol upgrade.  If on completion of a request, the
+ * response code is 101 (switch protocols), then the org.eclipse.jetty.io.Connection
+ * request attribute is checked to see if there is a new Connection instance. If so,
+ * the new connection is returned from {@link #handle()} and is used for future
+ * handling of the underlying connection.   Note that for switching protocols that
+ * don't use 101 responses (eg CONNECT), the response should be sent and then the
+ * status code changed to 101 before returning from the handler.  Implementors
+ * of new Connection types should be careful to extract any buffered data from
+ * (HttpParser)http.getParser()).getHeaderBuffer() and
+ * (HttpParser)http.getParser()).getBodyBuffer() to initialise their new connection.
+ * </p>
+ *
+ */
+public abstract class AbstractHttpConnection  extends AbstractConnection
+{
+    private static final Logger LOG = Log.getLogger(AbstractHttpConnection.class);
+
+    private static final int UNKNOWN = -2;
+    private static final ThreadLocal<AbstractHttpConnection> __currentConnection = new ThreadLocal<AbstractHttpConnection>();
+
+    private int _requests;
+
+    protected final Connector _connector;
+    protected final Server _server;
+    protected final HttpURI _uri;
+
+    protected final Parser _parser;
+    protected final HttpFields _requestFields;
+    protected final Request _request;
+    protected volatile ServletInputStream _in;
+
+    protected final Generator _generator;
+    protected final HttpFields _responseFields;
+    protected final Response _response;
+    protected volatile Output _out;
+    protected volatile OutputWriter _writer;
+    protected volatile PrintWriter _printWriter;
+
+    int _include;
+
+    private Object _associatedObject; // associated object
+
+    private int _version = UNKNOWN;
+
+    private String _charset;
+    private boolean _expect = false;
+    private boolean _expect100Continue = false;
+    private boolean _expect102Processing = false;
+    private boolean _head = false;
+    private boolean _host = false;
+    private boolean _delayedHandling=false;
+    private boolean _earlyEOF = false;
+
+    /* ------------------------------------------------------------ */
+    public static AbstractHttpConnection getCurrentConnection()
+    {
+        return __currentConnection.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected static void setCurrentConnection(AbstractHttpConnection connection)
+    {
+        __currentConnection.set(connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    public AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server)
+    {
+        super(endpoint);
+        _uri = StringUtil.__UTF8.equals(URIUtil.__CHARSET)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
+        _connector = connector;
+        HttpBuffers ab = (HttpBuffers)_connector;
+        _parser = newHttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler());
+        _requestFields = new HttpFields();
+        _responseFields = new HttpFields();
+        _request = new Request(this);
+        _response = new Response(this);
+        _generator = newHttpGenerator(ab.getResponseBuffers(), endpoint);
+        _generator.setSendServerVersion(server.getSendServerVersion());
+        _server = server;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected AbstractHttpConnection(Connector connector, EndPoint endpoint, Server server,
+            Parser parser, Generator generator, Request request)
+    {
+        super(endpoint);
+
+        _uri = URIUtil.__CHARSET.equals(StringUtil.__UTF8)?new HttpURI():new EncodedHttpURI(URIUtil.__CHARSET);
+        _connector = connector;
+        _parser = parser;
+        _requestFields = new HttpFields();
+        _responseFields = new HttpFields();
+        _request = request;
+        _response = new Response(this);
+        _generator = generator;
+        _generator.setSendServerVersion(server.getSendServerVersion());
+        _server = server;
+    }
+
+    protected HttpParser newHttpParser(Buffers requestBuffers, EndPoint endpoint, HttpParser.EventHandler requestHandler)
+    {
+        return new HttpParser(requestBuffers, endpoint, requestHandler);
+    }
+
+    protected HttpGenerator newHttpGenerator(Buffers responseBuffers, EndPoint endPoint)
+    {
+        return new HttpGenerator(responseBuffers, endPoint);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the parser used by this connection
+     */
+    public Parser getParser()
+    {
+        return _parser;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the number of requests handled by this connection
+     */
+    public int getRequests()
+    {
+        return _requests;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the associatedObject.
+     */
+    public Object getAssociatedObject()
+    {
+        return _associatedObject;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param associatedObject The associatedObject to set.
+     */
+    public void setAssociatedObject(Object associatedObject)
+    {
+        _associatedObject = associatedObject;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connector.
+     */
+    public Connector getConnector()
+    {
+        return _connector;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the requestFields.
+     */
+    public HttpFields getRequestFields()
+    {
+        return _requestFields;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the responseFields.
+     */
+    public HttpFields getResponseFields()
+    {
+        return _responseFields;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Find out if the request supports CONFIDENTIAL security.
+     * @param request the incoming HTTP request
+     * @return the result of calling {@link Connector#isConfidential(Request)}, or false
+     * if there is no connector
+     */
+    public boolean isConfidential(Request request)
+    {
+        return _connector != null && _connector.isConfidential(request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Find out if the request supports INTEGRAL security.
+     * @param request the incoming HTTP request
+     * @return the result of calling {@link Connector#isIntegral(Request)}, or false
+     * if there is no connector
+     */
+    public boolean isIntegral(Request request)
+    {
+        return _connector != null && _connector.isIntegral(request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return <code>false</code> (this method is not yet implemented)
+     */
+    public boolean getResolveNames()
+    {
+        return _connector.getResolveNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the request.
+     */
+    public Request getRequest()
+    {
+        return _request;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the response.
+     */
+    public Response getResponse()
+    {
+        return _response;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the inputStream from the connection.
+     * <p>
+     * If the associated response has the Expect header set to 100 Continue,
+     * then accessing the input stream indicates that the handler/servlet
+     * is ready for the request body and thus a 100 Continue response is sent.
+     *
+     * @return The input stream for this connection.
+     * The stream will be created if it does not already exist.
+     * @throws IOException if the input stream cannot be retrieved
+     */
+    public ServletInputStream getInputStream() throws IOException
+    {
+        // If the client is expecting 100 CONTINUE, then send it now.
+        if (_expect100Continue)
+        {
+            // is content missing?
+            if (((HttpParser)_parser).getHeaderBuffer()==null || ((HttpParser)_parser).getHeaderBuffer().length()<2)
+            {
+                if (_generator.isCommitted())
+                    throw new IllegalStateException("Committed before 100 Continues");
+
+                ((HttpGenerator)_generator).send1xx(HttpStatus.CONTINUE_100);
+            }
+            _expect100Continue=false;
+        }
+
+        if (_in == null)
+            _in = new HttpInput(AbstractHttpConnection.this);
+        return _in;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The output stream for this connection. The stream will be created if it does not already exist.
+     */
+    public ServletOutputStream getOutputStream()
+    {
+        if (_out == null)
+            _out = new Output();
+        return _out;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param encoding the PrintWriter encoding
+     * @return A {@link PrintWriter} wrapping the {@link #getOutputStream output stream}. The writer is created if it
+     *    does not already exist.
+     */
+    public PrintWriter getPrintWriter(String encoding)
+    {
+        getOutputStream();
+        if (_writer==null)
+        {
+            _writer=new OutputWriter();
+            if (_server.isUncheckedPrintWriter())
+                _printWriter=new UncheckedPrintWriter(_writer);
+            else
+                _printWriter = new PrintWriter(_writer)
+                {
+                    public void close()
+                    {
+                        synchronized (lock)
+                        {
+                            try
+                            {
+                                out.close();
+                            }
+                            catch (IOException e)
+                            {
+                                setError();
+                            }
+                        }
+                    }
+                };
+        }
+        _writer.setCharacterEncoding(encoding);
+        return _printWriter;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isResponseCommitted()
+    {
+        return _generator.isCommitted();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isEarlyEOF()
+    {
+        return _earlyEOF;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void reset()
+    {
+        _parser.reset();
+        _parser.returnBuffers(); // TODO maybe only on unhandle
+        _requestFields.clear();
+        _request.recycle();
+        _generator.reset();
+        _generator.returnBuffers();// TODO maybe only on unhandle
+        _responseFields.clear();
+        _response.recycle();
+        _uri.clear();
+        _writer=null;
+        _earlyEOF = false;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void handleRequest() throws IOException
+    {
+        boolean error = false;
+
+        String threadName=null;
+        Throwable async_exception=null;
+        try
+        {
+            if (LOG.isDebugEnabled())
+            {
+                threadName=Thread.currentThread().getName();
+                Thread.currentThread().setName(threadName+" - "+_uri);
+            }
+
+
+            // Loop here to handle async request redispatches.
+            // The loop is controlled by the call to async.unhandle in the
+            // finally block below.  If call is from a non-blocking connector,
+            // then the unhandle will return false only if an async dispatch has
+            // already happened when unhandle is called.   For a blocking connector,
+            // the wait for the asynchronous dispatch or timeout actually happens
+            // within the call to unhandle().
+
+            final Server server=_server;
+            boolean was_continuation=_request._async.isContinuation();
+            boolean handling=_request._async.handling() && server!=null && server.isRunning();
+            while (handling)
+            {
+                _request.setHandled(false);
+
+                String info=null;
+                try
+                {
+                    _uri.getPort();
+                    String path = null;
+
+                    try
+                    {
+                        path = _uri.getDecodedPath();
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
+                        LOG.ignore(e);
+                        path = _uri.getDecodedPath(StringUtil.__ISO_8859_1);
+                    }
+
+                    info=URIUtil.canonicalPath(path);
+                    if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT))
+                    {
+                        if (path==null && _uri.getScheme()!=null && _uri.getHost()!=null)
+                        {
+                            info="/";
+                            _request.setRequestURI("");
+                        }
+                        else
+                            throw new HttpException(400);
+                    }
+                    _request.setPathInfo(info);
+
+                    if (_out!=null)
+                        _out.reopen();
+
+                    if (_request._async.isInitial())
+                    {
+                        _request.setDispatcherType(DispatcherType.REQUEST);
+                        _connector.customize(_endp, _request);
+                        server.handle(this);
+                    }
+                    else
+                    {
+                        if (_request._async.isExpired()&&!was_continuation)
+                        {
+                            async_exception = (Throwable)_request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+                            _response.setStatus(500,async_exception==null?"Async Timeout":"Async Exception");
+                            _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
+                            _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, _response.getReason());
+                            _request.setDispatcherType(DispatcherType.ERROR);
+                            
+                            ErrorHandler eh = _request._async.getContextHandler().getErrorHandler();
+                            if (eh instanceof ErrorHandler.ErrorPageMapper)
+                            {
+                                String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_request._async.getRequest());
+                                if (error_page!=null)
+                                { 
+                                    AsyncContinuation.AsyncEventState state = _request._async.getAsyncEventState();
+                                    state.setPath(error_page);
+                                }
+                            }
+                        }
+                        else
+                            _request.setDispatcherType(DispatcherType.ASYNC);
+                        server.handleAsync(this);
+                    }
+                }
+                catch (ContinuationThrowable e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (EofException e)
+                {
+                    async_exception=e;
+                    LOG.debug(e);
+                    error=true;
+                    _request.setHandled(true);
+                    if (!_response.isCommitted())
+                        _generator.sendError(500, null, null, true);
+                }
+                catch (RuntimeIOException e)
+                {
+                    async_exception=e;
+                    LOG.debug(e);
+                    error=true;
+                    _request.setHandled(true);
+                }
+                catch (HttpException e)
+                {
+                    LOG.debug(e);
+                    error=true;
+                    _request.setHandled(true);
+                    _response.sendError(e.getStatus(), e.getReason());
+                }
+                catch (Throwable e)
+                {
+                    async_exception=e;
+                    LOG.warn(String.valueOf(_uri),e);
+                    error=true;
+                    _request.setHandled(true);
+                    _generator.sendError(info==null?400:500, null, null, true);
+                    
+                }
+                finally
+                {
+                    // Complete async requests 
+                    if (error && _request.isAsyncStarted())
+                        _request.getAsyncContinuation().errorComplete();
+                        
+                    was_continuation=_request._async.isContinuation();
+                    handling = !_request._async.unhandle() && server.isRunning() && _server!=null;
+                }
+            }
+        }
+        finally
+        {
+            if (threadName!=null)
+                Thread.currentThread().setName(threadName);
+
+            if (_request._async.isUncompleted())
+            {
+                
+                _request._async.doComplete(async_exception);
+
+                if (_expect100Continue)
+                {
+                    LOG.debug("100 continues not sent");
+                    // We didn't send 100 continues, but the latest interpretation
+                    // of the spec (see httpbis) is that the client will either
+                    // send the body anyway, or close.  So we no longer need to
+                    // do anything special here other than make the connection not persistent
+                    _expect100Continue = false;
+                    if (!_response.isCommitted())
+                        _generator.setPersistent(false);
+                }
+
+                if(_endp.isOpen())
+                {
+                    if (error)
+                    {
+                        _endp.shutdownOutput();
+                        _generator.setPersistent(false);
+                        if (!_generator.isComplete())
+                            _response.complete();
+                    }
+                    else
+                    {
+                        if (!_response.isCommitted() && !_request.isHandled())
+                            _response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                        _response.complete();
+                        if (_generator.isPersistent())
+                            _connector.persist(_endp);
+                    }
+                }
+                else
+                {
+                    _response.complete();
+                }
+
+                _request.setHandled(true);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public abstract Connection handle() throws IOException;
+
+    /* ------------------------------------------------------------ */
+    public void commitResponse(boolean last) throws IOException
+    {
+        if (!_generator.isCommitted())
+        {
+            _generator.setResponse(_response.getStatus(), _response.getReason());
+            try
+            {
+                // If the client was expecting 100 continues, but we sent something
+                // else, then we need to close the connection
+                if (_expect100Continue && _response.getStatus()!=100)
+                    _generator.setPersistent(false);
+                _generator.completeHeader(_responseFields, last);
+            }
+            catch(RuntimeException e)
+            {
+                LOG.warn("header full: " + e);
+
+                _response.reset();
+                _generator.reset();
+                _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
+                _generator.completeHeader(_responseFields,Generator.LAST);
+                _generator.complete();
+                throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500);
+            }
+
+        }
+        if (last)
+            _generator.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void completeResponse() throws IOException
+    {
+        if (!_generator.isCommitted())
+        {
+            _generator.setResponse(_response.getStatus(), _response.getReason());
+            try
+            {
+                _generator.completeHeader(_responseFields, Generator.LAST);
+            }
+            catch(RuntimeException e)
+            {
+                LOG.warn("header full: "+e);
+                LOG.debug(e);
+
+                _response.reset();
+                _generator.reset();
+                _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500,null);
+                _generator.completeHeader(_responseFields,Generator.LAST);
+                _generator.complete();
+                throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500);
+            }
+        }
+
+        _generator.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flushResponse() throws IOException
+    {
+        try
+        {
+            commitResponse(Generator.MORE);
+            _generator.flushBuffer();
+        }
+        catch(IOException e)
+        {
+            throw (e instanceof EofException) ? e:new EofException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Generator getGenerator()
+    {
+        return _generator;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIncluding()
+    {
+        return _include>0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void include()
+    {
+        _include++;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void included()
+    {
+        _include--;
+        if (_out!=null)
+            _out.reopen();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _generator.isIdle() && (_parser.isIdle() || _delayedHandling);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.io.Connection#isSuspended()
+     */
+    public boolean isSuspended()
+    {
+        return _request.getAsyncContinuation().isSuspended();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        LOG.debug("closed {}",this);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isExpecting100Continues()
+    {
+        return _expect100Continue;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isExpecting102Processing()
+    {
+        return _expect102Processing;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxIdleTime()
+    {
+        if (_connector.isLowResources() && _endp.getMaxIdleTime()==_connector.getMaxIdleTime())
+            return _connector.getLowResourceMaxIdleTime();
+        if (_endp.getMaxIdleTime()>0)
+            return _endp.getMaxIdleTime();
+        return _connector.getMaxIdleTime();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return String.format("%s,g=%s,p=%s,r=%d",
+                super.toString(),
+                _generator,
+                _parser,
+                _requests);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
+    {
+        uri=uri.asImmutableBuffer();
+
+        _host = false;
+        _expect = false;
+        _expect100Continue=false;
+        _expect102Processing=false;
+        _delayedHandling=false;
+        _charset=null;
+
+        if(_request.getTimeStamp()==0)
+            _request.setTimeStamp(System.currentTimeMillis());
+        _request.setMethod(method.toString());
+
+        try
+        {
+            _head=false;
+            switch (HttpMethods.CACHE.getOrdinal(method))
+            {
+              case HttpMethods.CONNECT_ORDINAL:
+                  _uri.parseConnect(uri.array(), uri.getIndex(), uri.length());
+                  break;
+
+              case HttpMethods.HEAD_ORDINAL:
+                  _head=true;
+                  _uri.parse(uri.array(), uri.getIndex(), uri.length());
+                  break;
+
+              default:
+                  _uri.parse(uri.array(), uri.getIndex(), uri.length());
+            }
+
+            _request.setUri(_uri);
+
+            if (version==null)
+            {
+                _request.setProtocol(HttpVersions.HTTP_0_9);
+                _version=HttpVersions.HTTP_0_9_ORDINAL;
+            }
+            else
+            {
+                version= HttpVersions.CACHE.get(version);
+                if (version==null)
+                    throw new HttpException(HttpStatus.BAD_REQUEST_400,null);
+                _version = HttpVersions.CACHE.getOrdinal(version);
+                if (_version <= 0) _version = HttpVersions.HTTP_1_0_ORDINAL;
+                _request.setProtocol(version.toString());
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.debug(e);
+            if (e instanceof HttpException)
+                throw (HttpException)e;
+            throw new HttpException(HttpStatus.BAD_REQUEST_400,null,e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void parsedHeader(Buffer name, Buffer value) throws IOException
+    {
+        int ho = HttpHeaders.CACHE.getOrdinal(name);
+        switch (ho)
+        {
+            case HttpHeaders.HOST_ORDINAL:
+                // TODO check if host matched a host in the URI.
+                _host = true;
+                break;
+
+            case HttpHeaders.EXPECT_ORDINAL:
+                if (_version>=HttpVersions.HTTP_1_1_ORDINAL)
+                {
+                    value = HttpHeaderValues.CACHE.lookup(value);
+                    switch(HttpHeaderValues.CACHE.getOrdinal(value))
+                    {
+                        case HttpHeaderValues.CONTINUE_ORDINAL:
+                            _expect100Continue=_generator instanceof HttpGenerator;
+                            break;
+
+                        case HttpHeaderValues.PROCESSING_ORDINAL:
+                            _expect102Processing=_generator instanceof HttpGenerator;
+                            break;
+
+                        default:
+                            String[] values = value.toString().split(",");
+                            for  (int i=0;values!=null && i<values.length;i++)
+                            {
+                                CachedBuffer cb=HttpHeaderValues.CACHE.get(values[i].trim());
+                                if (cb==null)
+                                    _expect=true;
+                                else
+                                {
+                                    switch(cb.getOrdinal())
+                                    {
+                                        case HttpHeaderValues.CONTINUE_ORDINAL:
+                                            _expect100Continue=_generator instanceof HttpGenerator;
+                                            break;
+                                        case HttpHeaderValues.PROCESSING_ORDINAL:
+                                            _expect102Processing=_generator instanceof HttpGenerator;
+                                            break;
+                                        default:
+                                            _expect=true;
+                                    }
+                                }
+                            }
+                    }
+                }
+                break;
+
+            case HttpHeaders.ACCEPT_ENCODING_ORDINAL:
+            case HttpHeaders.USER_AGENT_ORDINAL:
+                value = HttpHeaderValues.CACHE.lookup(value);
+                break;
+
+            case HttpHeaders.CONTENT_TYPE_ORDINAL:
+                value = MimeTypes.CACHE.lookup(value);
+                _charset=MimeTypes.getCharsetFromContentType(value);
+                break;
+        }
+
+        _requestFields.add(name, value);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void headerComplete() throws IOException
+    {
+        // Handle idle race
+        if (_endp.isOutputShutdown())
+        {
+            _endp.close();
+            return;
+        }
+        
+        _requests++;
+        _generator.setVersion(_version);
+        switch (_version)
+        {
+            case HttpVersions.HTTP_0_9_ORDINAL:
+                break;
+            case HttpVersions.HTTP_1_0_ORDINAL:
+                _generator.setHead(_head);
+                if (_parser.isPersistent())
+                {
+                    _responseFields.add(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.KEEP_ALIVE_BUFFER);
+                    _generator.setPersistent(true);
+                }
+                else if (HttpMethods.CONNECT.equals(_request.getMethod()))
+                {
+                    _generator.setPersistent(true);
+                    _parser.setPersistent(true);
+                }
+
+                if (_server.getSendDateHeader())
+                    _generator.setDate(_request.getTimeStampBuffer());
+                break;
+
+            case HttpVersions.HTTP_1_1_ORDINAL:
+                _generator.setHead(_head);
+
+                if (!_parser.isPersistent())
+                {
+                    _responseFields.add(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+                    _generator.setPersistent(false);
+                }
+                if (_server.getSendDateHeader())
+                    _generator.setDate(_request.getTimeStampBuffer());
+
+                if (!_host)
+                {
+                    LOG.debug("!host {}",this);
+                    _generator.setResponse(HttpStatus.BAD_REQUEST_400, null);
+                    _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
+                    _generator.completeHeader(_responseFields, true);
+                    _generator.complete();
+                    return;
+                }
+
+                if (_expect)
+                {
+                    LOG.debug("!expectation {}",this);
+                    _generator.setResponse(HttpStatus.EXPECTATION_FAILED_417, null);
+                    _responseFields.put(HttpHeaders.CONNECTION_BUFFER, HttpHeaderValues.CLOSE_BUFFER);
+                    _generator.completeHeader(_responseFields, true);
+                    _generator.complete();
+                    return;
+                }
+
+                break;
+            default:
+        }
+
+        if(_charset!=null)
+            _request.setCharacterEncodingUnchecked(_charset);
+
+        // Either handle now or wait for first content
+        if ((((HttpParser)_parser).getContentLength()<=0 && !((HttpParser)_parser).isChunking())||_expect100Continue)
+            handleRequest();
+        else
+            _delayedHandling=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void content(Buffer buffer) throws IOException
+    {
+        if (_delayedHandling)
+        {
+            _delayedHandling=false;
+            handleRequest();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void messageComplete(long contentLength) throws IOException
+    {
+        if (_delayedHandling)
+        {
+            _delayedHandling=false;
+            handleRequest();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void earlyEOF()
+    {
+        _earlyEOF = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class RequestHandler extends HttpParser.EventHandler
+    {
+        /*
+         *
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startRequest(org.eclipse.io.Buffer,
+         *      org.eclipse.io.Buffer, org.eclipse.io.Buffer)
+         */
+        @Override
+        public void startRequest(Buffer method, Buffer uri, Buffer version) throws IOException
+        {
+            AbstractHttpConnection.this.startRequest(method, uri, version);
+        }
+
+        /*
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#parsedHeaderValue(org.eclipse.io.Buffer)
+         */
+        @Override
+        public void parsedHeader(Buffer name, Buffer value) throws IOException
+        {
+            AbstractHttpConnection.this.parsedHeader(name, value);
+        }
+
+        /*
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#headerComplete()
+         */
+        @Override
+        public void headerComplete() throws IOException
+        {
+            AbstractHttpConnection.this.headerComplete();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#content(int, org.eclipse.io.Buffer)
+         */
+        @Override
+        public void content(Buffer ref) throws IOException
+        {
+            AbstractHttpConnection.this.content(ref);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * (non-Javadoc)
+         *
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#messageComplete(int)
+         */
+        @Override
+        public void messageComplete(long contentLength) throws IOException
+        {
+            AbstractHttpConnection.this.messageComplete(contentLength);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * (non-Javadoc)
+         *
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#startResponse(org.eclipse.io.Buffer, int,
+         *      org.eclipse.io.Buffer)
+         */
+        @Override
+        public void startResponse(Buffer version, int status, Buffer reason)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Bad request!: "+version+" "+status+" "+reason);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * (non-Javadoc)
+         *
+         * @see org.eclipse.jetty.server.server.HttpParser.EventHandler#earlyEOF()
+         */
+        @Override
+        public void earlyEOF()
+        {
+            AbstractHttpConnection.this.earlyEOF();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class Output extends HttpOutput
+    {
+        Output()
+        {
+            super(AbstractHttpConnection.this);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see java.io.OutputStream#close()
+         */
+        @Override
+        public void close() throws IOException
+        {
+            if (isClosed())
+                return;
+
+            if (!isIncluding() && !super._generator.isCommitted())
+                commitResponse(Generator.LAST);
+            else
+                flushResponse();
+
+            super.close();
+        }
+
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see java.io.OutputStream#flush()
+         */
+        @Override
+        public void flush() throws IOException
+        {
+            if (!super._generator.isCommitted())
+                commitResponse(Generator.MORE);
+            super.flush();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletOutputStream#print(java.lang.String)
+         */
+        @Override
+        public void print(String s) throws IOException
+        {
+            if (isClosed())
+                throw new IOException("Closed");
+            PrintWriter writer=getPrintWriter(null);
+            writer.print(s);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendResponse(Buffer response) throws IOException
+        {
+            ((HttpGenerator)super._generator).sendResponse(response);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendContent(Object content) throws IOException
+        {
+            Resource resource=null;
+
+            if (isClosed())
+                throw new IOException("Closed");
+
+            if (super._generator.isWritten())
+                throw new IllegalStateException("!empty");
+
+            // Convert HTTP content to content
+            if (content instanceof HttpContent)
+            {
+                HttpContent httpContent = (HttpContent) content;
+                Buffer contentType = httpContent.getContentType();
+                if (contentType != null && !_responseFields.containsKey(HttpHeaders.CONTENT_TYPE_BUFFER))
+                {
+                    String enc = _response.getSetCharacterEncoding();
+                    if(enc==null)
+                        _responseFields.add(HttpHeaders.CONTENT_TYPE_BUFFER, contentType);
+                    else
+                    {
+                        if(contentType instanceof CachedBuffer)
+                        {
+                            CachedBuffer content_type = ((CachedBuffer)contentType).getAssociate(enc);
+                            if(content_type!=null)
+                                _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, content_type);
+                            else
+                            {
+                                _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
+                                        contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= "));
+                            }
+                        }
+                        else
+                        {
+                            _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER,
+                                    contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= "));
+                        }
+                    }
+                }
+                if (httpContent.getContentLength() > 0)
+                    _responseFields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER, httpContent.getContentLength());
+                Buffer lm = httpContent.getLastModified();
+                long lml=httpContent.getResource().lastModified();
+                if (lm != null)
+                {
+                    _responseFields.put(HttpHeaders.LAST_MODIFIED_BUFFER, lm);
+                }
+                else if (httpContent.getResource()!=null)
+                {
+                    if (lml!=-1)
+                        _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, lml);
+                }
+                
+                Buffer etag=httpContent.getETag();
+                if (etag!=null)
+                    _responseFields.put(HttpHeaders.ETAG_BUFFER,etag);
+
+                
+                boolean direct=_connector instanceof NIOConnector && ((NIOConnector)_connector).getUseDirectBuffers() && !(_connector instanceof SslConnector);
+                content = direct?httpContent.getDirectBuffer():httpContent.getIndirectBuffer();
+                if (content==null)
+                    content=httpContent.getInputStream();
+            }
+            else if (content instanceof Resource)
+            {
+                resource=(Resource)content;
+                _responseFields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER, resource.lastModified());
+                content=resource.getInputStream();
+            }
+
+            // Process content.
+            if (content instanceof Buffer)
+            {
+                super._generator.addContent((Buffer) content, Generator.LAST);
+                commitResponse(Generator.LAST);
+            }
+            else if (content instanceof InputStream)
+            {
+                InputStream in = (InputStream)content;
+
+                try
+                {
+                    int max = super._generator.prepareUncheckedAddContent();
+                    Buffer buffer = super._generator.getUncheckedBuffer();
+
+                    int len=buffer.readFrom(in,max);
+
+                    while (len>=0)
+                    {
+                        super._generator.completeUncheckedAddContent();
+                        _out.flush();
+
+                        max = super._generator.prepareUncheckedAddContent();
+                        buffer = super._generator.getUncheckedBuffer();
+                        len=buffer.readFrom(in,max);
+                    }
+                    super._generator.completeUncheckedAddContent();
+                    _out.flush();
+                }
+                finally
+                {
+                    if (resource!=null)
+                        resource.release();
+                    else
+                        in.close();
+                }
+            }
+            else
+                throw new IllegalArgumentException("unknown content type?");
+
+
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class OutputWriter extends HttpWriter
+    {
+        OutputWriter()
+        {
+            super(AbstractHttpConnection.this._out);
+        }
+    }
+
+
+}
diff --git a/src/java/org/eclipse/jetty/server/AsyncContinuation.java b/src/java/org/eclipse/jetty/server/AsyncContinuation.java
new file mode 100644
index 0000000..d364156
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/AsyncContinuation.java
@@ -0,0 +1,1160 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationThrowable;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Timeout;
+
+/* ------------------------------------------------------------ */
+/** Implementation of Continuation and AsyncContext interfaces
+ * 
+ */
+public class AsyncContinuation implements AsyncContext, Continuation
+{
+    private static final Logger LOG = Log.getLogger(AsyncContinuation.class);
+
+    private final static long DEFAULT_TIMEOUT=30000L;
+    
+    private final static ContinuationThrowable __exception = new ContinuationThrowable();
+    
+    // STATES:
+    //               handling()    suspend()     unhandle()    resume()       complete()  doComplete()
+    //                             startAsync()                dispatch()   
+    // IDLE          DISPATCHED      
+    // DISPATCHED                  ASYNCSTARTED  UNCOMPLETED
+    // ASYNCSTARTED                              ASYNCWAIT     REDISPATCHING  COMPLETING
+    // REDISPATCHING                             REDISPATCHED  
+    // ASYNCWAIT                                               REDISPATCH     COMPLETING
+    // REDISPATCH    REDISPATCHED
+    // REDISPATCHED                ASYNCSTARTED  UNCOMPLETED
+    // COMPLETING    UNCOMPLETED                 UNCOMPLETED
+    // UNCOMPLETED                                                                        COMPLETED
+    // COMPLETED
+    private static final int __IDLE=0;         // Idle request
+    private static final int __DISPATCHED=1;   // Request dispatched to filter/servlet
+    private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
+    private static final int __REDISPATCHING=3;// resumed while dispatched
+    private static final int __ASYNCWAIT=4;    // Suspended and parked
+    private static final int __REDISPATCH=5;   // Has been scheduled
+    private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
+    private static final int __COMPLETING=7;   // complete while dispatched
+    private static final int __UNCOMPLETED=8;  // Request is completable
+    private static final int __COMPLETED=9;    // Request is complete
+    
+    /* ------------------------------------------------------------ */
+    protected AbstractHttpConnection _connection;
+    private List<AsyncListener> _lastAsyncListeners;
+    private List<AsyncListener> _asyncListeners;
+    private List<ContinuationListener> _continuationListeners;
+
+    /* ------------------------------------------------------------ */
+    private int _state;
+    private boolean _initial;
+    private boolean _resumed;
+    private boolean _expired;
+    private volatile boolean _responseWrapped;
+    private long _timeoutMs=DEFAULT_TIMEOUT;
+    private AsyncEventState _event;
+    private volatile long _expireAt;    
+    private volatile boolean _continuation;
+    
+    /* ------------------------------------------------------------ */
+    protected AsyncContinuation()
+    {
+        _state=__IDLE;
+        _initial=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void setConnection(final AbstractHttpConnection connection)
+    {
+        synchronized(this)
+        {
+            _connection=connection;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addListener(AsyncListener listener)
+    {
+        synchronized(this)
+        {
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<AsyncListener>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
+    {
+        synchronized(this)
+        {
+            // TODO handle the request/response ???
+            if (_asyncListeners==null)
+                _asyncListeners=new ArrayList<AsyncListener>();
+            _asyncListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addContinuationListener(ContinuationListener listener)
+    {
+        synchronized(this)
+        {
+            if (_continuationListeners==null)
+                _continuationListeners=new ArrayList<ContinuationListener>();
+            _continuationListeners.add(listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeout(long ms)
+    {
+        synchronized(this)
+        {
+            _timeoutMs=ms;
+        }
+    } 
+
+    /* ------------------------------------------------------------ */
+    public long getTimeout()
+    {
+        synchronized(this)
+        {
+            return _timeoutMs;
+        }
+    } 
+
+    /* ------------------------------------------------------------ */
+    public AsyncEventState getAsyncEventState()
+    {
+        synchronized(this)
+        {
+            return _event;
+        }
+    } 
+   
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#keepWrappers()
+     */
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
+     */
+    public boolean isResponseWrapped()
+    {
+        return _responseWrapped;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#isInitial()
+     */
+    public boolean isInitial()
+    {
+        synchronized(this)
+        {
+            return _initial;
+        }
+    }
+    
+    public boolean isContinuation()
+    {
+        return _continuation;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#isSuspended()
+     */
+    public boolean isSuspended()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __REDISPATCHING:
+                case __COMPLETING:
+                case __ASYNCWAIT:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isSuspending()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDispatchable()
+    {
+        synchronized(this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCH:
+                case __REDISPATCHED:
+                case __REDISPATCHING:
+                case __COMPLETING:
+                    return true;
+                    
+                default:
+                    return false;   
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        synchronized (this)
+        {
+            return super.toString()+"@"+getStatusString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getStatusString()
+    {
+        synchronized (this)
+        {
+            return
+            ((_state==__IDLE)?"IDLE":
+                (_state==__DISPATCHED)?"DISPATCHED":
+                    (_state==__ASYNCSTARTED)?"ASYNCSTARTED":
+                        (_state==__ASYNCWAIT)?"ASYNCWAIT":
+                            (_state==__REDISPATCHING)?"REDISPATCHING":
+                                (_state==__REDISPATCH)?"REDISPATCH":
+                                    (_state==__REDISPATCHED)?"REDISPATCHED":
+                                        (_state==__COMPLETING)?"COMPLETING":
+                                            (_state==__UNCOMPLETED)?"UNCOMPLETED":
+                                                (_state==__COMPLETED)?"COMPLETE":
+                                                    ("UNKNOWN?"+_state))+
+            (_initial?",initial":"")+
+            (_resumed?",resumed":"")+
+            (_expired?",expired":"");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return false if the handling of the request should not proceed
+     */
+    protected boolean handling()
+    {
+        synchronized (this)
+        {
+            _continuation=false;
+            
+            switch(_state)
+            {
+                case __IDLE:
+                    _initial=true;
+                    _state=__DISPATCHED;
+                    if (_lastAsyncListeners!=null)
+                        _lastAsyncListeners.clear();
+                    if (_asyncListeners!=null)
+                        _asyncListeners.clear();
+                    else
+                    {
+                        _asyncListeners=_lastAsyncListeners;
+                        _lastAsyncListeners=null;
+                    }
+                    return true;
+                    
+                case __COMPLETING:
+                    _state=__UNCOMPLETED;
+                    return false;
+
+                case __ASYNCWAIT:
+                    return false;
+                    
+                case __REDISPATCH:
+                    _state=__REDISPATCHED;
+                    return true;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#suspend(long)
+     */
+    private void doSuspend(final ServletContext context,
+            final ServletRequest request,
+            final ServletResponse response)
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    _resumed=false;
+                    _expired=false;
+
+                    if (_event==null || request!=_event.getSuppliedRequest() || response != _event.getSuppliedResponse() || context != _event.getServletContext())
+                        _event=new AsyncEventState(context,request,response);
+                    else
+                    {
+                        _event._dispatchContext=null;
+                        _event._pathInContext=null;
+                    }
+                    _state=__ASYNCSTARTED;
+                    List<AsyncListener> recycle=_lastAsyncListeners;
+                    _lastAsyncListeners=_asyncListeners;
+                    _asyncListeners=recycle;
+                    if (_asyncListeners!=null)
+                        _asyncListeners.clear();
+                    break;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (_lastAsyncListeners!=null)
+        {
+            for (AsyncListener listener : _lastAsyncListeners)
+            {
+                try
+                {
+                    listener.onStartAsync(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Signal that the HttpConnection has finished handling the request.
+     * For blocking connectors, this call may block if the request has
+     * been suspended (startAsync called).
+     * @return true if handling is complete, false if the request should 
+     * be handled again (eg because of a resume that happened before unhandle was called)
+     */
+    protected boolean unhandle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCHED:
+                case __DISPATCHED:
+                    _state=__UNCOMPLETED;
+                    return true;
+
+                case __IDLE:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __ASYNCSTARTED:
+                    _initial=false;
+                    _state=__ASYNCWAIT;
+                    scheduleTimeout(); // could block and change state.
+                    if (_state==__ASYNCWAIT)
+                        return true;
+                    else if (_state==__COMPLETING)
+                    {
+                        _state=__UNCOMPLETED;
+                        return true;
+                    }         
+                    _initial=false;
+                    _state=__REDISPATCHED;
+                    return false; 
+
+                case __REDISPATCHING:
+                    _initial=false;
+                    _state=__REDISPATCHED;
+                    return false; 
+
+                case __COMPLETING:
+                    _initial=false;
+                    _state=__UNCOMPLETED;
+                    return true;
+
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch()
+    {
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                    _state=__REDISPATCHING;
+                    _resumed=true;
+                    return;
+
+                case __ASYNCWAIT:
+                    dispatch=!_expired;
+                    _state=__REDISPATCH;
+                    _resumed=true;
+                    break;
+                    
+                case __REDISPATCH:
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (dispatch)
+        {
+            cancelTimeout();
+            scheduleDispatch();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void expired()
+    {
+        final List<ContinuationListener> cListeners;
+        final List<AsyncListener> aListeners;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    cListeners=_continuationListeners;
+                    aListeners=_asyncListeners;
+                    break;
+                default:
+                    cListeners=null;
+                    aListeners=null;
+                    return;
+            }
+            _expired=true;
+        }
+        
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    listener.onTimeout(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.debug(e);
+                    _connection.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+                    break;
+                }
+            }
+        }
+        if (cListeners!=null)
+        {
+            for (ContinuationListener listener : cListeners)
+            {
+                try
+                {
+                    listener.onTimeout(this);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __ASYNCWAIT:
+                    dispatch();
+                    break;
+                    
+                default:
+                    if (!_continuation)
+                        _expired=false;
+            }
+        }
+
+        scheduleDispatch();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    public void complete()
+    {
+        // just like resume, except don't set _resumed=true;
+        boolean dispatch=false;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    throw new IllegalStateException(this.getStatusString());
+
+                case __ASYNCSTARTED:
+                    _state=__COMPLETING;
+                    return;
+                    
+                case __ASYNCWAIT:
+                    _state=__COMPLETING;
+                    dispatch=!_expired;
+                    break;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (dispatch)
+        {
+            cancelTimeout();
+            scheduleDispatch();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    public void errorComplete()
+    {
+        // just like complete except can overrule a prior dispatch call;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __REDISPATCHING:
+                case __ASYNCSTARTED:
+                    _state=__COMPLETING;
+                    _resumed=false;
+                    return;
+                    
+                case __COMPLETING:
+                    return;
+                    
+                default:
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException 
+    {
+        try
+        {
+            // TODO inject
+            return clazz.newInstance();
+        }
+        catch(Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.ServletRequest#complete()
+     */
+    protected void doComplete(Throwable ex)
+    {
+        final List<ContinuationListener> cListeners;
+        final List<AsyncListener> aListeners;
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __UNCOMPLETED:
+                    _state=__COMPLETED;
+                    cListeners=_continuationListeners;
+                    aListeners=_asyncListeners;
+                    break;
+                    
+                default:
+                    cListeners=null;
+                    aListeners=null;
+                    throw new IllegalStateException(this.getStatusString());
+            }
+        }
+        
+        if (aListeners!=null)
+        {
+            for (AsyncListener listener : aListeners)
+            {
+                try
+                {
+                    if (ex!=null)
+                    {
+                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
+                        _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,ex.getMessage());
+                        listener.onError(_event);
+                    }
+                    else
+                        listener.onComplete(_event);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+        if (cListeners!=null)
+        {
+            for (ContinuationListener listener : cListeners)
+            {
+                try
+                {
+                    listener.onComplete(this);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void recycle()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __DISPATCHED:
+                case __REDISPATCHED:
+                    throw new IllegalStateException(getStatusString());
+                default:
+                    _state=__IDLE;
+            }
+            _initial = true;
+            _resumed=false;
+            _expired=false;
+            _responseWrapped=false;
+            cancelTimeout();
+            _timeoutMs=DEFAULT_TIMEOUT;
+            _continuationListeners=null;
+        }
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public void cancel()
+    {
+        synchronized (this)
+        {
+            cancelTimeout();
+            _continuationListeners=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void scheduleDispatch()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (!endp.isBlocking())
+        {
+            ((AsyncEndPoint)endp).asyncDispatch();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void scheduleTimeout()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (_timeoutMs>0)
+        {
+            if (endp.isBlocking())
+            {
+                synchronized(this)
+                {
+                    _expireAt = System.currentTimeMillis()+_timeoutMs;
+                    long wait=_timeoutMs;
+                    while (_expireAt>0 && wait>0 && _connection.getServer().isRunning())
+                    {
+                        try
+                        {
+                            this.wait(wait);
+                        }
+                        catch (InterruptedException e)
+                        {
+                            LOG.ignore(e);
+                        }
+                        wait=_expireAt-System.currentTimeMillis();
+                    }
+
+                    if (_expireAt>0 && wait<=0 && _connection.getServer().isRunning())
+                    {
+                        expired();
+                    }
+                }            
+            }
+            else
+            {
+                ((AsyncEndPoint)endp).scheduleTimeout(_event._timeout,_timeoutMs);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void cancelTimeout()
+    {
+        EndPoint endp=_connection.getEndPoint();
+        if (endp.isBlocking())
+        {
+            synchronized(this)
+            {
+                _expireAt=0;
+                this.notifyAll();
+            }
+        }
+        else 
+        {
+            final AsyncEventState event=_event;
+            if (event!=null)
+            {
+                ((AsyncEndPoint)endp).cancelTimeout(event._timeout);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isCompleting()
+    {
+        synchronized (this)
+        {
+            return _state==__COMPLETING;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    boolean isUncompleted()
+    {
+        synchronized (this)
+        {
+            return _state==__UNCOMPLETED;
+        }
+    } 
+    
+    /* ------------------------------------------------------------ */
+    public boolean isComplete()
+    {
+        synchronized (this)
+        {
+            return _state==__COMPLETED;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncStarted()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __ASYNCSTARTED:
+                case __REDISPATCHING:
+                case __REDISPATCH:
+                case __ASYNCWAIT:
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsync()
+    {
+        synchronized (this)
+        {
+            switch(_state)
+            {
+                case __IDLE:
+                case __DISPATCHED:
+                case __UNCOMPLETED:
+                case __COMPLETED:
+                    return false;
+
+                default:
+                    return true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch(ServletContext context, String path)
+    {
+        _event._dispatchContext=context;
+        _event.setPath(path);
+        dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dispatch(String path)
+    {
+        _event.setPath(path);
+        dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Request getBaseRequest()
+    {
+        return _connection.getRequest();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletRequest getRequest()
+    {
+        if (_event!=null)
+            return _event.getSuppliedRequest();
+        return _connection.getRequest();
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getResponse()
+    {
+        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
+            return _event.getSuppliedResponse();
+        return _connection.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void start(final Runnable run)
+    {
+        final AsyncEventState event=_event;
+        if (event!=null)
+        {
+            _connection.getServer().getThreadPool().dispatch(new Runnable()
+            {
+                public void run()
+                {
+                    ((Context)event.getServletContext()).getContextHandler().handle(run);
+                }
+            });
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean hasOriginalRequestAndResponse()
+    {
+        synchronized (this)
+        {
+            return (_event!=null && _event.getSuppliedRequest()==_connection._request && _event.getSuppliedResponse()==_connection._response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        final AsyncEventState event=_event;
+        if (event!=null)
+            return ((Context)event.getServletContext()).getContextHandler();
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#isResumed()
+     */
+    public boolean isResumed()
+    {
+        synchronized (this)
+        {
+            return _resumed;
+        }
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#isExpired()
+     */
+    public boolean isExpired()
+    {
+        synchronized (this)
+        {
+            return _expired;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#resume()
+     */
+    public void resume()
+    {
+        dispatch();
+    }
+    
+
+
+    /* ------------------------------------------------------------ */
+    protected void startAsync(final ServletContext context,
+            final ServletRequest request,
+            final ServletResponse response)
+    {
+        synchronized (this)
+        {
+            _responseWrapped=!(response instanceof Response);
+            doSuspend(context,request,response);
+            if (request instanceof HttpServletRequest)
+            {
+                _event._pathInContext = URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void startAsync()
+    {
+        _responseWrapped=false;
+        _continuation=false;
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());  
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#suspend()
+     */
+    public void suspend(ServletResponse response)
+    {
+        _continuation=true;
+        _responseWrapped=!(response instanceof Response);
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response); 
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Continuation#suspend()
+     */
+    public void suspend()
+    {
+        _responseWrapped=false;
+        _continuation=true;
+        doSuspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());       
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
+     */
+    public ServletResponse getServletResponse()
+    {
+        if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
+            return _event.getSuppliedResponse();
+        return _connection.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _connection.getRequest().getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _connection.getRequest().removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _connection.getRequest().setAttribute(name,attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.continuation.Continuation#undispatch()
+     */
+    public void undispatch()
+    {
+        if (isSuspended())
+        {
+            if (LOG.isDebugEnabled())
+                throw new ContinuationThrowable();
+            else
+                throw __exception;
+        }
+        throw new IllegalStateException("!suspended");
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class AsyncTimeout extends Timeout.Task implements Runnable
+    {
+            @Override
+            public void expired()
+            {
+                AsyncContinuation.this.expired();
+            }
+
+            @Override
+            public void run()
+            {
+                AsyncContinuation.this.expired();
+            }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public class AsyncEventState extends AsyncEvent
+    {
+        private final ServletContext _suspendedContext;
+        private ServletContext _dispatchContext;
+        private String _pathInContext;
+        private Timeout.Task _timeout=  new AsyncTimeout();
+        
+        public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
+        {
+            super(AsyncContinuation.this, request,response);
+            _suspendedContext=context;
+            // Get the base request So we can remember the initial paths
+            Request r=_connection.getRequest();
+ 
+            // If we haven't been async dispatched before
+            if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
+            {
+                // We are setting these attributes during startAsync, when the spec implies that 
+                // they are only available after a call to AsyncContext.dispatch(...);
+                
+                // have we been forwarded before?
+                String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
+                if (uri!=null)
+                {
+                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
+                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
+                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
+                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
+                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
+                }
+                else
+                {
+                    r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
+                    r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
+                    r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
+                    r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
+                    r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
+                }
+            }
+        }
+        
+        public ServletContext getSuspendedContext()
+        {
+            return _suspendedContext;
+        }
+        
+        public ServletContext getDispatchContext()
+        {
+            return _dispatchContext;
+        }
+        
+        public ServletContext getServletContext()
+        {
+            return _dispatchContext==null?_suspendedContext:_dispatchContext;
+        }
+        
+        public void setPath(String path)
+        {
+            _pathInContext=path;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The path in the context
+         */
+        public String getPath()
+        {
+            return _pathInContext;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/AsyncHttpConnection.java b/src/java/org/eclipse/jetty/server/AsyncHttpConnection.java
new file mode 100644
index 0000000..1feeabd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/AsyncHttpConnection.java
@@ -0,0 +1,220 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Asychronous Server HTTP connection
+ *
+ */
+public class AsyncHttpConnection extends AbstractHttpConnection implements AsyncConnection
+{
+    private final static int NO_PROGRESS_INFO = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_INFO",100);
+    private final static int NO_PROGRESS_CLOSE = Integer.getInteger("org.mortbay.jetty.NO_PROGRESS_CLOSE",200);
+
+    private static final Logger LOG = Log.getLogger(AsyncHttpConnection.class);
+    private int _total_no_progress;
+    private final AsyncEndPoint _asyncEndp;
+    private boolean _readInterested = true;
+
+    public AsyncHttpConnection(Connector connector, EndPoint endpoint, Server server)
+    {
+        super(connector,endpoint,server);
+        _asyncEndp=(AsyncEndPoint)endpoint;
+    }
+
+    @Override
+    public Connection handle() throws IOException
+    {
+        Connection connection = this;
+        boolean some_progress=false;
+        boolean progress=true;
+
+        try
+        {
+            setCurrentConnection(this);
+
+            // don't check for idle while dispatched (unless blocking IO is done).
+            _asyncEndp.setCheckForIdle(false);
+
+
+            // While progress and the connection has not changed
+            while (progress && connection==this)
+            {
+                progress=false;
+                try
+                {
+                    // Handle resumed request
+                    if (_request._async.isAsync())
+                    {
+                       if (_request._async.isDispatchable())
+                           handleRequest();
+                    }
+                    // else Parse more input
+                    else if (!_parser.isComplete() && _parser.parseAvailable())
+                        progress=true;
+
+                    // Generate more output
+                    if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown() && !_request.getAsyncContinuation().isAsyncStarted())
+                        if (_generator.flushBuffer()>0)
+                            progress=true;
+
+                    // Flush output
+                    _endp.flush();
+
+                    // Has any IO been done by the endpoint itself since last loop
+                    if (_asyncEndp.hasProgressed())
+                        progress=true;
+                }
+                catch (HttpException e)
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("uri="+_uri);
+                        LOG.debug("fields="+_requestFields);
+                        LOG.debug(e);
+                    }
+                    progress=true;
+                    _generator.sendError(e.getStatus(), e.getReason(), null, true);
+                }
+                finally
+                {
+                    some_progress|=progress;
+                    //  Is this request/response round complete and are fully flushed?
+                    boolean parserComplete = _parser.isComplete();
+                    boolean generatorComplete = _generator.isComplete();
+                    boolean complete = parserComplete && generatorComplete;
+                    if (parserComplete)
+                    {
+                        if (generatorComplete)
+                        {
+                            // Reset the parser/generator
+                            progress=true;
+
+                            // look for a switched connection instance?
+                            if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)
+                            {
+                                Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection");
+                                if (switched!=null)
+                                    connection=switched;
+                            }
+
+                            reset();
+
+                            // TODO Is this still required?
+                            if (!_generator.isPersistent() && !_endp.isOutputShutdown())
+                            {
+                                LOG.warn("Safety net oshut!!!  IF YOU SEE THIS, PLEASE RAISE BUGZILLA");
+                                _endp.shutdownOutput();
+                            }
+                        }
+                        else
+                        {
+                            // We have finished parsing, but not generating so
+                            // we must not be interested in reading until we
+                            // have finished generating and we reset the generator
+                            _readInterested = false;
+                            LOG.debug("Disabled read interest while writing response {}", _endp);
+                        }
+                    }
+
+                    if (!complete && _request.getAsyncContinuation().isAsyncStarted())
+                    {
+                        // The request is suspended, so even though progress has been made,
+                        // exit the while loop by setting progress to false
+                        LOG.debug("suspended {}",this);
+                        progress=false;
+                    }
+                }
+            }
+        }
+        finally
+        {
+            setCurrentConnection(null);
+
+            // If we are not suspended
+            if (!_request.getAsyncContinuation().isAsyncStarted())
+            {
+                // return buffers
+                _parser.returnBuffers();
+                _generator.returnBuffers();
+
+                // reenable idle checking unless request is suspended
+                _asyncEndp.setCheckForIdle(true);
+            }
+
+            // Safety net to catch spinning
+            if (some_progress)
+                _total_no_progress=0;
+            else
+            {
+                _total_no_progress++;
+                if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE))
+                    LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);
+                if (NO_PROGRESS_CLOSE>0 && _total_no_progress==NO_PROGRESS_CLOSE)
+                {
+                    LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);
+                    if (_endp instanceof SelectChannelEndPoint)
+                        ((SelectChannelEndPoint)_endp).getChannel().close();
+                }
+            }
+        }
+        return connection;
+    }
+
+    public void onInputShutdown() throws IOException
+    {
+        // If we don't have a committed response and we are not suspended
+        if (_generator.isIdle() && !_request.getAsyncContinuation().isSuspended())
+        {
+            // then no more can happen, so close.
+            _endp.close();
+        }
+
+        // Make idle parser seek EOF
+        if (_parser.isIdle())
+            _parser.setPersistent(false);
+    }
+
+    @Override
+    public void reset()
+    {
+        _readInterested = true;
+        LOG.debug("Enabled read interest {}", _endp);
+        super.reset();
+    }
+
+    @Override
+    public boolean isSuspended()
+    {
+        return !_readInterested || super.isSuspended();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java b/src/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
new file mode 100644
index 0000000..e9acf9b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/AsyncNCSARequestLog.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * An asynchronously writing NCSA Request Log
+ */
+public class AsyncNCSARequestLog extends NCSARequestLog
+{
+    private static final Logger LOG = Log.getLogger(AsyncNCSARequestLog.class);
+    private final BlockingQueue<String> _queue;
+    private transient WriterThread _thread;
+    private boolean _warnedFull;
+
+    public AsyncNCSARequestLog()
+    {
+        this(null,null);
+    }
+    
+    public AsyncNCSARequestLog(BlockingQueue<String> queue)
+    {
+        this(null,queue);
+    }
+
+    public AsyncNCSARequestLog(String filename)
+    {
+        this(filename,null);
+    }
+    
+    public AsyncNCSARequestLog(String filename,BlockingQueue<String> queue)
+    {
+        super(filename);
+        if (queue==null)
+            queue=new BlockingArrayQueue<String>(1024);
+        _queue=queue;
+    }
+
+    private class WriterThread extends Thread
+    {
+        WriterThread()
+        {
+            setName("AsyncNCSARequestLog@"+Integer.toString(AsyncNCSARequestLog.this.hashCode(),16));
+        }
+        
+        @Override
+        public void run()
+        {
+            while (isRunning())
+            {
+                try
+                {
+                    String log = _queue.poll(10,TimeUnit.SECONDS);
+                    if (log!=null)
+                        AsyncNCSARequestLog.super.write(log);
+                    
+                    while(!_queue.isEmpty())
+                    {
+                        log=_queue.poll();
+                        if (log!=null)
+                            AsyncNCSARequestLog.super.write(log);
+                    }
+                }
+                catch (IOException e)
+                {
+                    LOG.warn(e);
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        super.doStart();
+        _thread = new WriterThread();
+        _thread.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _thread.interrupt();
+        _thread.join();
+        super.doStop();
+        _thread=null;
+    }
+
+    @Override
+    protected void write(String log) throws IOException
+    {
+        if (!_queue.offer(log))
+        {
+            if (_warnedFull)
+                LOG.warn("Log Queue overflow");
+            _warnedFull=true;
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/Authentication.java b/src/java/org/eclipse/jetty/server/Authentication.java
new file mode 100644
index 0000000..4729005
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Authentication.java
@@ -0,0 +1,155 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** The Authentication state of a request.
+ * <p>
+ * The Authentication state can be one of several sub-types that
+ * reflects where the request is in the many different authentication
+ * cycles. Authentication might not yet be checked or it might be checked
+ * and failed, checked and deferred or succeeded. 
+ * 
+ */
+public interface Authentication
+{
+    /* ------------------------------------------------------------ */
+    /** A successful Authentication with User information.
+     */
+    public interface User extends Authentication
+    {
+        String getAuthMethod();
+        UserIdentity getUserIdentity(); 
+        boolean isUserInRole(UserIdentity.Scope scope,String role);
+        void logout();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A wrapped authentication with methods provide the
+     * wrapped request/response for use by the application
+     */
+    public interface Wrapped extends Authentication
+    {
+        HttpServletRequest getHttpServletRequest();
+        HttpServletResponse getHttpServletResponse();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A deferred authentication with methods to progress 
+     * the authentication process.
+     */
+    public interface Deferred extends Authentication
+    {
+        /* ------------------------------------------------------------ */
+        /** Authenticate if possible without sending a challenge.
+         * This is used to check credentials that have been sent for 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request);
+
+        /* ------------------------------------------------------------ */
+        /** Authenticate and possibly send a challenge.
+         * This is used to initiate authentication for previously 
+         * non-manditory authentication.
+         * @return The new Authentication state.
+         */
+        Authentication authenticate(ServletRequest request,ServletResponse response);
+        
+        
+        /* ------------------------------------------------------------ */
+        /** Login with the LOGIN authenticator
+         * @param username
+         * @param password
+         * @return The new Authentication state
+         */
+        Authentication login(String username,Object password,ServletRequest request);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Authentication Response sent state.
+     * Responses are sent by authenticators either to issue an
+     * authentication challenge or on successful authentication in
+     * order to redirect the user to the original URL.
+     */
+    public interface ResponseSent extends Authentication
+    { 
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** An Authentication Challenge has been sent.
+     */
+    public interface Challenge extends ResponseSent
+    { 
+    }
+
+    /* ------------------------------------------------------------ */
+    /** An Authentication Failure has been sent.
+     */
+    public interface Failure extends ResponseSent
+    { 
+    }
+
+    public interface SendSuccess extends ResponseSent
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Unauthenticated state.
+     * <p> 
+     * This convenience instance is for non mandatory authentication where credentials
+     * have been presented and checked, but failed authentication. 
+     */
+    public final static Authentication UNAUTHENTICATED = new Authentication(){@Override
+    public String toString(){return "UNAUTHENTICATED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication not checked
+     * <p>
+     * This convenience instance us for non mandatory authentication when no 
+     * credentials are present to be checked.
+     */
+    public final static Authentication NOT_CHECKED = new Authentication(){@Override
+    public String toString(){return "NOT CHECKED";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication challenge sent.
+     * <p>
+     * This convenience instance is for when an authentication challenge has been sent.
+     */
+    public final static Authentication SEND_CONTINUE = new Authentication.Challenge(){@Override
+    public String toString(){return "CHALLENGE";}};
+
+    /* ------------------------------------------------------------ */
+    /** Authentication failure sent.
+     * <p>
+     * This convenience instance is for when an authentication failure has been sent.
+     */
+    public final static Authentication SEND_FAILURE = new Authentication.Failure(){@Override
+    public String toString(){return "FAILURE";}};
+    public final static Authentication SEND_SUCCESS = new SendSuccess(){@Override
+    public String toString(){return "SEND_SUCCESS";}};
+}
diff --git a/src/java/org/eclipse/jetty/server/BlockingHttpConnection.java b/src/java/org/eclipse/jetty/server/BlockingHttpConnection.java
new file mode 100644
index 0000000..cc0d7e1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/BlockingHttpConnection.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.Parser;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Blocking Server HTTP Connection
+ */
+public class BlockingHttpConnection extends AbstractHttpConnection
+{
+    private static final Logger LOG = Log.getLogger(BlockingHttpConnection.class);
+
+    public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server)
+    {
+        super(connector,endpoint,server);
+    }
+
+    public BlockingHttpConnection(Connector connector, EndPoint endpoint, Server server, Parser parser, Generator generator, Request request)
+    {
+        super(connector,endpoint,server,parser,generator,request);
+    }
+
+    @Override
+    protected void handleRequest() throws IOException
+    {
+        super.handleRequest();
+    }
+
+    public Connection handle() throws IOException
+    {
+        Connection connection = this;
+
+        try
+        {
+            setCurrentConnection(this);
+
+            // do while the endpoint is open
+            // AND the connection has not changed
+            while (_endp.isOpen() && connection==this)
+            {
+                try
+                {
+                    // If we are not ended then parse available
+                    if (!_parser.isComplete() && !_endp.isInputShutdown())
+                        _parser.parseAvailable();
+
+                    // Do we have more generating to do?
+                    // Loop here because some writes may take multiple steps and
+                    // we need to flush them all before potentially blocking in the
+                    // next loop.
+                    if (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown())
+                        _generator.flushBuffer();
+
+                    // Flush buffers
+                    _endp.flush();
+                }
+                catch (HttpException e)
+                {
+                    if (LOG.isDebugEnabled())
+                    {
+                        LOG.debug("uri="+_uri);
+                        LOG.debug("fields="+_requestFields);
+                        LOG.debug(e);
+                    }
+                    _generator.sendError(e.getStatus(), e.getReason(), null, true);
+                    _parser.reset();
+                    _endp.shutdownOutput();
+                }
+                finally
+                {
+                    //  Is this request/response round complete and are fully flushed?
+                    if (_parser.isComplete() && _generator.isComplete())
+                    {
+                        // Reset the parser/generator
+                        reset();
+
+                        // look for a switched connection instance?
+                        if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)
+                        {
+                            Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection");
+                            if (switched!=null)
+                                connection=switched;
+                        }
+
+                        // TODO Is this required?
+                        if (!_generator.isPersistent() && !_endp.isOutputShutdown())
+                        {
+                            LOG.warn("Safety net oshut!!! Please open a bugzilla");
+                            _endp.shutdownOutput();
+                        }
+                    }
+                    
+                    // If we don't have a committed response and we are not suspended
+                    if (_endp.isInputShutdown() && _generator.isIdle() && !_request.getAsyncContinuation().isSuspended())
+                    {
+                        // then no more can happen, so close.
+                        _endp.close();
+                    }
+                }
+            }
+
+            return connection;
+        }
+        finally
+        {
+            setCurrentConnection(null);
+            _parser.returnBuffers();
+            _generator.returnBuffers();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/Connector.java b/src/java/org/eclipse/jetty/server/Connector.java
new file mode 100644
index 0000000..11cc290
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Connector.java
@@ -0,0 +1,387 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/** HTTP Connector.
+ * Implementations of this interface provide connectors for the HTTP protocol.
+ * A connector receives requests (normally from a socket) and calls the 
+ * handle method of the Handler object.  These operations are performed using
+ * threads from the ThreadPool set on the connector.
+ * 
+ * When a connector is registered with an instance of Server, then the server
+ * will set itself as both the ThreadPool and the Handler.  Note that a connector
+ * can be used without a Server if a thread pool and handler are directly provided.
+ * 
+ * 
+ * 
+ */
+/**
+ * @author gregw
+ *
+ */
+public interface Connector extends LifeCycle
+{ 
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the name of the connector. Defaults to the HostName:port
+     */
+    String getName();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Opens the connector 
+     * @throws IOException
+     */
+    void open() throws IOException;
+
+    /* ------------------------------------------------------------ */
+    void close() throws IOException;
+
+    /* ------------------------------------------------------------ */
+    void setServer(Server server);
+    
+    /* ------------------------------------------------------------ */
+    Server getServer();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the request header buffer size in bytes.
+     */
+    int getRequestHeaderSize();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the buffer to be used for request headers.
+     * @param size The size in bytes.
+     */
+    void setRequestHeaderSize(int size);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the response header buffer size in bytes.
+     */
+    int getResponseHeaderSize();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the buffer to be used for request headers.
+     * @param size The size in bytes.
+     */
+    void setResponseHeaderSize(int size);
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return factory for request buffers
+     */
+    Buffers getRequestBuffers();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return factory for response buffers
+     */
+    Buffers getResponseBuffers();
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the requestBufferSize.
+     */
+    int getRequestBufferSize();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the content buffer for receiving requests. 
+     * These buffers are only used for active connections that have
+     * requests with bodies that will not fit within the header buffer.
+     * @param requestBufferSize The requestBufferSize to set.
+     */
+    void setRequestBufferSize(int requestBufferSize);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the responseBufferSize.
+     */
+    int getResponseBufferSize();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the size of the content buffer for sending responses. 
+     * These buffers are only used for active connections that are sending 
+     * responses with bodies that will not fit within the header buffer.
+     * @param responseBufferSize The responseBufferSize to set.
+     */
+    void setResponseBufferSize(int responseBufferSize);
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The port to use when redirecting a request if a data constraint of integral is 
+     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
+     */
+    int getIntegralPort();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The schema to use when redirecting a request if a data constraint of integral is 
+     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
+     */
+    String getIntegralScheme();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request A request
+     * @return true if the request is integral. This normally means the https schema has been used.
+     */
+    boolean isIntegral(Request request);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The port to use when redirecting a request if a data constraint of confidential is 
+     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
+     */
+    int getConfidentialPort();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The schema to use when redirecting a request if a data constraint of confidential is 
+     * required. See {@link org.eclipse.jetty.util.security.Constraint#getDataConstraint()}
+     */
+    String getConfidentialScheme();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param request A request
+     * @return true if the request is confidential. This normally means the https schema has been used.
+     */
+    boolean isConfidential(Request request);
+
+    /* ------------------------------------------------------------ */
+    /** Customize a request for an endpoint.
+     * Called on every request to allow customization of the request for
+     * the particular endpoint (eg security properties from a SSL connection).
+     * @param endpoint
+     * @param request
+     * @throws IOException
+     */
+    void customize(EndPoint endpoint, Request request) throws IOException;
+
+    /* ------------------------------------------------------------ */
+    /** Persist an endpoint.
+     * Called after every request if the connection is to remain open.
+     * @param endpoint
+     * @throws IOException
+     */
+    void persist(EndPoint endpoint) throws IOException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The hostname representing the interface to which 
+     * this connector will bind, or null for all interfaces.
+     */
+    String getHost();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the hostname of the interface to bind to.
+     * @param hostname The hostname representing the interface to which 
+     * this connector will bind, or null for all interfaces.
+     */
+    void setHost(String hostname);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param port The port to listen of for connections or 0 if any available
+     * port may be used.
+     */
+    void setPort(int port);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The configured port for the connector or 0 if any available
+     * port may be used.
+     */
+    int getPort();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The actual port the connector is listening on or
+     * -1 if it has not been opened, or -2 if it has been closed.
+     */
+    int getLocalPort();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Max Idle time for connections in milliseconds
+     */
+    int getMaxIdleTime();
+    
+    /**
+     * @param ms Max Idle time for connections in milliseconds
+     */
+    void setMaxIdleTime(int ms);
+    
+    /* ------------------------------------------------------------ */
+    int getLowResourceMaxIdleTime();
+    void setLowResourceMaxIdleTime(int ms);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the underlying socket, channel, buffer etc. for the connector.
+     */
+    Object getConnection();
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if names resolution should be done.
+     */
+    boolean getResolveNames();
+    
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Get the number of requests handled by this connector
+     * since last call of statsReset(). If setStatsOn(false) then this
+     * is undefined.
+     */
+    public int getRequests();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connectionsDurationTotal.
+     */
+    public long getConnectionsDurationTotal();
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Number of connections accepted by the server since
+     * statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnections() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Number of connections currently open that were opened
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsOpen() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Maximum number of connections opened simultaneously
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsOpenMax() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Maximum duration in milliseconds of an open connection
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public long getConnectionsDurationMax();
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Mean duration in milliseconds of open connections
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsDurationMean() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Standard deviation of duration in milliseconds of
+     * open connections since statsReset() called. Undefined if
+     * setStatsOn(false).
+     */
+    public double getConnectionsDurationStdDev() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Mean number of requests per connection
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsRequestsMean() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Standard Deviation of number of requests per connection
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public double getConnectionsRequestsStdDev() ;
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Maximum number of requests per connection
+     * since statsReset() called. Undefined if setStatsOn(false).
+     */
+    public int getConnectionsRequestsMax();
+
+    /* ------------------------------------------------------------ */
+    /** Reset statistics.
+     */
+    public void statsReset();
+    
+    /* ------------------------------------------------------------ */
+    public void setStatsOn(boolean on);
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return True if statistics collection is turned on.
+     */
+    public boolean getStatsOn();
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Timestamp stats were started at.
+     */
+    public long getStatsOnMs();
+    
+
+    /* ------------------------------------------------------------ */
+    /** Check if low on resources.
+     * For most connectors, low resources is measured by calling 
+     * {@link ThreadPool#isLowOnThreads()} on the connector threadpool
+     * or the server threadpool if there is no connector threadpool.
+     * <p>
+     * For blocking connectors, low resources is used to trigger
+     * usage of {@link #getLowResourceMaxIdleTime()} for the timeout
+     * of an idle connection.
+     * <p>
+     * for non-blocking connectors, the number of connections is
+     * used instead of this method, to select the timeout of an 
+     * idle connection.
+     * <p>
+     * For all connectors, low resources is used to trigger the 
+     * usage of {@link #getLowResourceMaxIdleTime()} for read and 
+     * write operations.
+     * 
+     * @return true if this connector is low on resources.
+     */
+    public boolean isLowResources();
+}
diff --git a/src/java/org/eclipse/jetty/server/CookieCutter.java b/src/java/org/eclipse/jetty/server/CookieCutter.java
new file mode 100644
index 0000000..53d3773
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/CookieCutter.java
@@ -0,0 +1,335 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+import java.util.Locale;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Cookie parser
+ * <p>Optimized stateful cookie parser.  Cookies fields are added with the
+ * {@link #addCookieField(String)} method and parsed on the next subsequent
+ * call to {@link #getCookies()}.
+ * If the added fields are identical to those last added (as strings), then the 
+ * cookies are not re parsed.
+ * 
+ *
+ */
+public class CookieCutter
+{
+    private static final Logger LOG = Log.getLogger(CookieCutter.class);
+
+
+    private Cookie[] _cookies;
+    private Cookie[] _lastCookies;
+    Object _lazyFields;
+    int _fields;
+    
+    public CookieCutter()
+    {  
+    }
+    
+    public Cookie[] getCookies()
+    {
+        if (_cookies!=null)
+            return _cookies;
+        
+        if (_lastCookies!=null &&
+            _lazyFields!=null &&
+            _fields==LazyList.size(_lazyFields))
+            _cookies=_lastCookies;
+        else
+            parseFields();
+        _lastCookies=_cookies;
+        return _cookies;
+    }
+    
+    public void setCookies(Cookie[] cookies)
+    {
+        _cookies=cookies;
+        _lastCookies=null;
+        _lazyFields=null;
+        _fields=0;
+    }
+    
+    public void reset()
+    {
+        _cookies=null;
+        _fields=0;
+    }
+    
+    public void addCookieField(String f)
+    {
+        if (f==null)
+            return;
+        f=f.trim();
+        if (f.length()==0)
+            return;
+            
+        if (LazyList.size(_lazyFields)>_fields)
+        {
+            if (f.equals(LazyList.get(_lazyFields,_fields)))
+            {
+                _fields++;
+                return;
+            }
+            
+            while (LazyList.size(_lazyFields)>_fields)
+                _lazyFields=LazyList.remove(_lazyFields,_fields);
+        }
+        _cookies=null;
+        _lastCookies=null;
+        _lazyFields=LazyList.add(_lazyFields,_fields++,f);
+    }
+    
+    
+    protected void parseFields()
+    {
+        _lastCookies=null;
+        _cookies=null;
+        
+        Object cookies = null;
+
+        int version = 0;
+
+        // delete excess fields
+        while (LazyList.size(_lazyFields)>_fields)
+            _lazyFields=LazyList.remove(_lazyFields,_fields);
+        
+        // For each cookie field
+        for (int f=0;f<_fields;f++)
+        {
+            String hdr = LazyList.get(_lazyFields,f);
+            
+            // Parse the header
+            String name = null;
+            String value = null;
+
+            Cookie cookie = null;
+
+            boolean invalue=false;
+            boolean quoted=false;
+            boolean escaped=false;
+            int tokenstart=-1;
+            int tokenend=-1;
+            for (int i = 0, length = hdr.length(), last=length-1; i < length; i++)
+            {
+                char c = hdr.charAt(i);
+                
+                // Handle quoted values for name or value
+                if (quoted)
+                {
+                    if (escaped)
+                    {
+                        escaped=false;
+                        continue;
+                    }
+                    
+                    switch (c)
+                    {
+                        case '"':
+                            tokenend=i;
+                            quoted=false;
+
+                            // handle quote as last character specially
+                            if (i==last)
+                            {
+                                if (invalue)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                            }
+                            break;
+                            
+                        case '\\':
+                            escaped=true;
+                            continue;
+                        default:
+                            continue;
+                    }
+                }
+                else
+                {
+                    // Handle name and value state machines
+                    if (invalue)
+                    {
+                        // parse the value
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                            // case ',':
+                                if (tokenstart>=0)
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                else
+                                    value="";
+                                tokenstart = -1;
+                                invalue=false;
+                                break;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    value = hdr.substring(tokenstart, tokenend+1);
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                    else
+                    {
+                        // parse the name
+                        switch (c)
+                        {
+                            case ' ':
+                            case '\t':
+                                continue;
+                                
+                            case '"':
+                                if (tokenstart<0)
+                                {
+                                    quoted=true;
+                                    tokenstart=i;
+                                }
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+
+                            case ';':
+                            // case ',':
+                                if (tokenstart>=0)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                }
+                                tokenstart = -1;
+                                break;
+
+                            case '=':
+                                if (tokenstart>=0)
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                tokenstart = -1;
+                                invalue=true;
+                                continue;
+                                
+                            default:
+                                if (tokenstart<0)
+                                    tokenstart=i;
+                                tokenend=i;
+                                if (i==last)
+                                {
+                                    name = hdr.substring(tokenstart, tokenend+1);
+                                    value = "";
+                                    break;
+                                }
+                                continue;
+                        }
+                    }
+                }
+
+                // If after processing the current character we have a value and a name, then it is a cookie
+                if (value!=null && name!=null)
+                {
+                    // TODO handle unquoting during parsing!  But quoting is uncommon
+                    name=QuotedStringTokenizer.unquoteOnly(name);
+                    value=QuotedStringTokenizer.unquoteOnly(value);
+                    
+                    try
+                    {
+                        if (name.startsWith("$"))
+                        {
+                            String lowercaseName = name.toLowerCase(Locale.ENGLISH);
+                            if ("$path".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setPath(value);
+                            }
+                            else if ("$domain".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setDomain(value);
+                            }
+                            else if ("$port".equals(lowercaseName))
+                            {
+                                if (cookie!=null)
+                                    cookie.setComment("$port="+value);
+                            }
+                            else if ("$version".equals(lowercaseName))
+                            {
+                                version = Integer.parseInt(value);
+                            }
+                        }
+                        else
+                        {
+                            cookie = new Cookie(name, value);
+                            if (version > 0)
+                                cookie.setVersion(version);
+                            cookies = LazyList.add(cookies, cookie);
+                        }
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                    }
+
+                    name = null;
+                    value = null;
+                }
+            }
+        }
+
+        _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class);
+        _lastCookies=_cookies;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/Dispatcher.java b/src/java/org/eclipse/jetty/server/Dispatcher.java
new file mode 100644
index 0000000..7c058a8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Dispatcher.java
@@ -0,0 +1,550 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.UrlEncoded;
+
+/* ------------------------------------------------------------ */
+/** Servlet RequestDispatcher.
+ * 
+ * 
+ */
+public class Dispatcher implements RequestDispatcher
+{
+    /** Dispatch include attribute names */
+    public final static String __INCLUDE_PREFIX="javax.servlet.include.";
+
+    /** Dispatch include attribute names */
+    public final static String __FORWARD_PREFIX="javax.servlet.forward.";
+
+    /** JSP attributes */
+    public final static String __JSP_FILE="org.apache.catalina.jsp_file";
+
+    /* ------------------------------------------------------------ */
+    private final ContextHandler _contextHandler;
+    private final String _uri;
+    private final String _path;
+    private final String _dQuery;
+    private final String _named;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextHandler
+     * @param uri
+     * @param pathInContext
+     * @param query
+     */
+    public Dispatcher(ContextHandler contextHandler, String uri, String pathInContext, String query)
+    {
+        _contextHandler=contextHandler;
+        _uri=uri;
+        _path=pathInContext;
+        _dQuery=query;
+        _named=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param contextHandler
+     * @param name
+     */
+    public Dispatcher(ContextHandler contextHandler,String name)
+        throws IllegalStateException
+    {
+        _contextHandler=contextHandler;
+        _named=name;
+        _uri=null;
+        _path=null;
+        _dQuery=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.FORWARD);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        forward(request, response, DispatcherType.ERROR);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+      
+        
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+            
+        
+        // TODO - allow stream or writer????
+        
+        final DispatcherType old_type = baseRequest.getDispatcherType();
+        final Attributes old_attr=baseRequest.getAttributes();
+        MultiMap old_params=baseRequest.getParameters();
+        try
+        {
+            baseRequest.setDispatcherType(DispatcherType.INCLUDE);
+            baseRequest.getConnection().include();
+            if (_named!=null)
+                _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            else 
+            {
+                String query=_dQuery;
+                
+                if (query!=null)
+                {
+                    // force parameter extraction
+                    if (old_params==null)
+                    {
+                        baseRequest.extractParameters();
+                        old_params=baseRequest.getParameters();
+                    }
+                    
+                    MultiMap parameters=new MultiMap();
+                    UrlEncoded.decodeTo(query,parameters,baseRequest.getCharacterEncoding());
+                    
+                    if (old_params!=null && old_params.size()>0)
+                    {
+                        // Merge parameters.
+                        Iterator iter = old_params.entrySet().iterator();
+                        while (iter.hasNext())
+                        {
+                            Map.Entry entry = (Map.Entry)iter.next();
+                            String name=(String)entry.getKey();
+                            Object values=entry.getValue();
+                            for (int i=0;i<LazyList.size(values);i++)
+                                parameters.add(name, LazyList.get(values, i));
+                        }
+                    }
+                    baseRequest.setParameters(parameters);
+                }
+                
+                IncludeAttributes attr = new IncludeAttributes(old_attr); 
+                
+                attr._requestURI=_uri;
+                attr._contextPath=_contextHandler.getContextPath();
+                attr._servletPath=null; // set by ServletHandler
+                attr._pathInfo=_path;
+                attr._query=query;
+                
+                baseRequest.setAttributes(attr);
+                
+                _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            }
+        }
+        finally
+        {
+            baseRequest.setAttributes(old_attr);
+            baseRequest.getConnection().included();
+            baseRequest.setParameters(old_params);
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException
+    {
+        Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+        Response base_response=baseRequest.getResponse();
+        response.resetBuffer();
+        base_response.fwdReset();
+       
+
+        if (!(request instanceof HttpServletRequest))
+            request = new ServletRequestHttpWrapper(request);
+        if (!(response instanceof HttpServletResponse))
+            response = new ServletResponseHttpWrapper(response);
+        
+        final boolean old_handled=baseRequest.isHandled();
+        final String old_uri=baseRequest.getRequestURI();
+        final String old_context_path=baseRequest.getContextPath();
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+        final String old_query=baseRequest.getQueryString();
+        final Attributes old_attr=baseRequest.getAttributes();
+        final DispatcherType old_type=baseRequest.getDispatcherType();
+        MultiMap<String> old_params=baseRequest.getParameters();
+        
+        try
+        {
+            baseRequest.setHandled(false);
+            baseRequest.setDispatcherType(dispatch);
+            
+            if (_named!=null)
+                _contextHandler.handle(_named,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+            else 
+            {
+                
+                // process any query string from the dispatch URL
+                String query=_dQuery;
+                if (query!=null)
+                {
+                    // force parameter extraction
+                    if (old_params==null)
+                    {
+                        baseRequest.extractParameters();
+                        old_params=baseRequest.getParameters();
+                    }
+                    
+                    baseRequest.mergeQueryString(query);
+                }
+                
+                ForwardAttributes attr = new ForwardAttributes(old_attr); 
+                
+                //If we have already been forwarded previously, then keep using the established 
+                //original value. Otherwise, this is the first forward and we need to establish the values.
+                //Note: the established value on the original request for pathInfo and
+                //for queryString is allowed to be null, but cannot be null for the other values.
+                if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null)
+                {
+                    attr._pathInfo=(String)old_attr.getAttribute(FORWARD_PATH_INFO);
+                    attr._query=(String)old_attr.getAttribute(FORWARD_QUERY_STRING);
+                    attr._requestURI=(String)old_attr.getAttribute(FORWARD_REQUEST_URI);
+                    attr._contextPath=(String)old_attr.getAttribute(FORWARD_CONTEXT_PATH);
+                    attr._servletPath=(String)old_attr.getAttribute(FORWARD_SERVLET_PATH);
+                }
+                else
+                {
+                    attr._pathInfo=old_path_info;
+                    attr._query=old_query;
+                    attr._requestURI=old_uri;
+                    attr._contextPath=old_context_path;
+                    attr._servletPath=old_servlet_path;
+                }     
+                
+                baseRequest.setRequestURI(_uri);
+                baseRequest.setContextPath(_contextHandler.getContextPath());
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(_uri);
+                baseRequest.setAttributes(attr);
+                
+                _contextHandler.handle(_path,baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
+                
+                if (!baseRequest.getAsyncContinuation().isAsyncStarted())
+                    commitResponse(response,baseRequest);
+            }
+        }
+        finally
+        {
+            baseRequest.setHandled(old_handled);
+            baseRequest.setRequestURI(old_uri);
+            baseRequest.setContextPath(old_context_path);
+            baseRequest.setServletPath(old_servlet_path);
+            baseRequest.setPathInfo(old_path_info);
+            baseRequest.setAttributes(old_attr);
+            baseRequest.setParameters(old_params);
+            baseRequest.setQueryString(old_query);
+            baseRequest.setDispatcherType(old_type);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private void commitResponse(ServletResponse response, Request baseRequest) throws IOException
+    {
+        if (baseRequest.getResponse().isWriting())
+        {
+            try
+            {
+                response.getWriter().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getOutputStream().close();
+            }
+        }
+        else
+        {
+            try
+            {
+                response.getOutputStream().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getWriter().close();
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class ForwardAttributes implements Attributes
+    {
+        final Attributes _attr;
+        
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+        
+        ForwardAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(FORWARD_PATH_INFO))    
+                    return _pathInfo;
+                if (key.equals(FORWARD_REQUEST_URI))  
+                    return _requestURI;
+                if (key.equals(FORWARD_SERVLET_PATH)) 
+                    return _servletPath;
+                if (key.equals(FORWARD_CONTEXT_PATH)) 
+                    return _contextPath;
+                if (key.equals(FORWARD_QUERY_STRING)) 
+                    return _query;
+            }
+            
+            if (key.startsWith(__INCLUDE_PREFIX))
+                return null;
+            
+            return _attr.getAttribute(key);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Enumeration getAttributeNames()
+        {
+            HashSet set=new HashSet();
+            Enumeration e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=(String)e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX) &&
+                    !name.startsWith(__FORWARD_PREFIX))
+                    set.add(name);
+            }
+            
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(FORWARD_PATH_INFO);
+                else
+                    set.remove(FORWARD_PATH_INFO);
+                set.add(FORWARD_REQUEST_URI);
+                set.add(FORWARD_SERVLET_PATH);
+                set.add(FORWARD_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(FORWARD_QUERY_STRING);
+                else
+                    set.remove(FORWARD_QUERY_STRING);
+            }
+
+            return Collections.enumeration(set);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(FORWARD_PATH_INFO))         
+                    _pathInfo=(String)value;
+                else if (key.equals(FORWARD_REQUEST_URI))  
+                    _requestURI=(String)value;
+                else if (key.equals(FORWARD_SERVLET_PATH)) 
+                    _servletPath=(String)value;
+                else if (key.equals(FORWARD_CONTEXT_PATH)) 
+                    _contextPath=(String)value;
+                else if (key.equals(FORWARD_QUERY_STRING)) 
+                    _query=(String)value;
+                
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value); 
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString() 
+        {
+            return "FORWARD+"+_attr.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private class IncludeAttributes implements Attributes
+    {
+        final Attributes _attr;
+        
+        String _requestURI;
+        String _contextPath;
+        String _servletPath;
+        String _pathInfo;
+        String _query;
+        
+        IncludeAttributes(Attributes attributes)
+        {
+            _attr=attributes;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /* ------------------------------------------------------------ */
+        /* ------------------------------------------------------------ */
+        public Object getAttribute(String key)
+        {
+            if (Dispatcher.this._named==null)
+            {
+                if (key.equals(INCLUDE_PATH_INFO))    return _pathInfo;
+                if (key.equals(INCLUDE_SERVLET_PATH)) return _servletPath;
+                if (key.equals(INCLUDE_CONTEXT_PATH)) return _contextPath;
+                if (key.equals(INCLUDE_QUERY_STRING)) return _query;
+                if (key.equals(INCLUDE_REQUEST_URI))  return _requestURI;
+            }
+            else if (key.startsWith(__INCLUDE_PREFIX)) 
+                    return null;
+            
+            
+            return _attr.getAttribute(key);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Enumeration getAttributeNames()
+        {
+            HashSet set=new HashSet();
+            Enumeration e=_attr.getAttributeNames();
+            while(e.hasMoreElements())
+            {
+                String name=(String)e.nextElement();
+                if (!name.startsWith(__INCLUDE_PREFIX))
+                    set.add(name);
+            }
+            
+            if (_named==null)
+            {
+                if (_pathInfo!=null)
+                    set.add(INCLUDE_PATH_INFO);
+                else
+                    set.remove(INCLUDE_PATH_INFO);
+                set.add(INCLUDE_REQUEST_URI);
+                set.add(INCLUDE_SERVLET_PATH);
+                set.add(INCLUDE_CONTEXT_PATH);
+                if (_query!=null)
+                    set.add(INCLUDE_QUERY_STRING);
+                else
+                    set.remove(INCLUDE_QUERY_STRING);
+            }
+            
+            return Collections.enumeration(set);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public void setAttribute(String key, Object value)
+        {
+            if (_named==null && key.startsWith("javax.servlet."))
+            {
+                if (key.equals(INCLUDE_PATH_INFO))         _pathInfo=(String)value;
+                else if (key.equals(INCLUDE_REQUEST_URI))  _requestURI=(String)value;
+                else if (key.equals(INCLUDE_SERVLET_PATH)) _servletPath=(String)value;
+                else if (key.equals(INCLUDE_CONTEXT_PATH)) _contextPath=(String)value;
+                else if (key.equals(INCLUDE_QUERY_STRING)) _query=(String)value;
+                else if (value==null)
+                    _attr.removeAttribute(key);
+                else
+                    _attr.setAttribute(key,value); 
+            }
+            else if (value==null)
+                _attr.removeAttribute(key);
+            else
+                _attr.setAttribute(key,value);
+        }
+        
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString() 
+        {
+            return "INCLUDE+"+_attr.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void clearAttributes()
+        {
+            throw new IllegalStateException();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void removeAttribute(String name)
+        {
+            setAttribute(name,null);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/Handler.java b/src/java/org/eclipse/jetty/server/Handler.java
new file mode 100644
index 0000000..cf56498
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Handler.java
@@ -0,0 +1,72 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A Jetty Server Handler.
+ * 
+ * A Handler instance is required by a {@link Server} to handle incoming
+ * HTTP requests.  A Handler may: <ul>
+ * <li>Completely generate the HTTP Response</li>
+ * <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
+ * <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
+ * </ul>
+ * 
+ * Handlers are passed the servlet API request and response object, but are 
+ * not Servlets.  The servlet container is implemented by handlers for 
+ * context, security, session and servlet that modify the request object 
+ * before passing it to the next stage of handling.
+ * 
+ */
+public interface Handler extends LifeCycle, Destroyable
+{
+    /* ------------------------------------------------------------ */
+    /** Handle a request.
+     * @param target The target of the request - either a URI or a name.
+     * @param baseRequest The original unwrapped request object.
+     * @param request The request either as the {@link Request}
+     * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} 
+     * method can be used access the Request object if required.
+     * @param response The response as the {@link Response}
+     * object or a wrapper of that request. The {@link AbstractHttpConnection#getCurrentConnection()} 
+     * method can be used access the Response object if required.
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException;
+    
+    public void setServer(Server server);
+    public Server getServer();
+    
+    public void destroy();
+    
+}
+
diff --git a/src/java/org/eclipse/jetty/server/HandlerContainer.java b/src/java/org/eclipse/jetty/server/HandlerContainer.java
new file mode 100644
index 0000000..4f9085e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/HandlerContainer.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/**
+ * A Handler that contains other Handlers.
+ * <p>
+ * The contained handlers may be one (see @{link {@link org.eclipse.jetty.server.handler.HandlerWrapper})
+ * or many (see {@link org.eclipse.jetty.server.handler.HandlerList} or {@link org.eclipse.jetty.server.handler.HandlerCollection}. 
+ *
+ */
+public interface HandlerContainer extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of handlers directly contained by this handler.
+     */
+    public Handler[] getHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of all handlers contained by this handler and it's children
+     */
+    public Handler[] getChildHandlers();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return array of all handlers contained by this handler and it's children of the passed type.
+     */
+    public Handler[] getChildHandlersByClass(Class<?> byclass);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param byclass
+     * @return first handler of all handlers contained by this handler and it's children of the passed type.
+     */
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass);
+}
diff --git a/src/java/org/eclipse/jetty/server/HttpInput.java b/src/java/org/eclipse/jetty/server/HttpInput.java
new file mode 100644
index 0000000..e0d48fc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/HttpInput.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+
+import javax.servlet.ServletInputStream;
+
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EofException;
+
+public class HttpInput extends ServletInputStream
+{
+    protected final AbstractHttpConnection _connection;
+    protected final HttpParser _parser;
+
+    /* ------------------------------------------------------------ */
+    public HttpInput(AbstractHttpConnection connection)
+    {
+        _connection=connection;
+        _parser=(HttpParser)connection.getParser();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see java.io.InputStream#read()
+     */
+    @Override
+    public int read() throws IOException
+    {
+        byte[] bytes = new byte[1];
+        int read = read(bytes, 0, 1);
+        return read < 0 ? -1 : 0xff & bytes[0];
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see java.io.InputStream#read(byte[], int, int)
+     */
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException
+    {
+        int l=-1;
+        Buffer content=_parser.blockForContent(_connection.getMaxIdleTime());
+        if (content!=null)
+            l= content.get(b, off, len);
+        else if (_connection.isEarlyEOF())
+            throw new EofException("early EOF");
+        return l;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int available() throws IOException
+    {
+        return _parser.available();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/HttpOutput.java b/src/java/org/eclipse/jetty/server/HttpOutput.java
new file mode 100644
index 0000000..311594a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/HttpOutput.java
@@ -0,0 +1,183 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletOutputStream;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.http.Generator;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+
+/** Output.
+ * 
+ * <p>
+ * Implements  {@link javax.servlet.ServletOutputStream} from the <code>javax.servlet</code> package.   
+ * </p>
+ * A {@link ServletOutputStream} implementation that writes content
+ * to a {@link AbstractGenerator}.   The class is designed to be reused
+ * and can be reopened after a close.
+ */
+public class HttpOutput extends ServletOutputStream 
+{
+    protected final AbstractHttpConnection _connection;
+    protected final AbstractGenerator _generator;
+    private boolean _closed;
+    private ByteArrayBuffer _onebyte;
+    
+    // These are held here for reuse by Writer
+    String _characterEncoding;
+    Writer _converter;
+    char[] _chars;
+    ByteArrayOutputStream2 _bytes;
+
+    /* ------------------------------------------------------------ */
+    public HttpOutput(AbstractHttpConnection connection)
+    {
+        _connection=connection;
+        _generator=(AbstractGenerator)connection.getGenerator();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxIdleTime()
+    {
+        return _connection.getMaxIdleTime();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isWritten()
+    {
+        return _generator.getContentWritten()>0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see java.io.OutputStream#close()
+     */
+    @Override
+    public void close() throws IOException
+    {
+        _closed=true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isClosed()
+    {
+        return _closed;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void reopen()
+    {
+        _closed=false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush() throws IOException
+    {
+        _generator.flush(getMaxIdleTime());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        write(new ByteArrayBuffer(b,off,len));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see java.io.OutputStream#write(byte[])
+     */
+    @Override
+    public void write(byte[] b) throws IOException
+    {
+        write(new ByteArrayBuffer(b));
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see java.io.OutputStream#write(int)
+     */
+    @Override
+    public void write(int b) throws IOException
+    {
+        if (_onebyte==null)
+            _onebyte=new ByteArrayBuffer(1);
+        else
+            _onebyte.clear();
+        _onebyte.put((byte)b);
+        write(_onebyte);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void write(Buffer buffer) throws IOException
+    {
+        if (_closed)
+            throw new IOException("Closed");
+        if (!_generator.isOpen())
+            throw new EofException();
+        
+        // Block until we can add _content.
+        while (_generator.isBufferFull())
+        {
+            _generator.blockForOutput(getMaxIdleTime());
+            if (_closed)
+                throw new IOException("Closed");
+            if (!_generator.isOpen())
+                throw new EofException();
+        }
+
+        // Add the _content
+        _generator.addContent(buffer, Generator.MORE);
+
+        // Have to flush and complete headers?
+        
+        if (_generator.isAllContentWritten())
+        {
+            flush();
+            close();
+        } 
+        else if (_generator.isBufferFull())
+            _connection.commitResponse(Generator.MORE);
+
+        // Block until our buffer is free
+        while (buffer.length() > 0 && _generator.isOpen())
+        {
+            _generator.blockForOutput(getMaxIdleTime());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.ServletOutputStream#print(java.lang.String)
+     */
+    @Override
+    public void print(String s) throws IOException
+    {
+        write(s.getBytes());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/HttpWriter.java b/src/java/org/eclipse/jetty/server/HttpWriter.java
new file mode 100644
index 0000000..9bff50d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/HttpWriter.java
@@ -0,0 +1,302 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.http.AbstractGenerator;
+import org.eclipse.jetty.util.ByteArrayOutputStream2;
+import org.eclipse.jetty.util.StringUtil;
+
+/** OutputWriter.
+ * A writer that can wrap a {@link HttpOutput} stream and provide
+ * character encodings.
+ *
+ * The UTF-8 encoding is done by this class and no additional 
+ * buffers or Writers are used.
+ * The UTF-8 code was inspired by http://javolution.org
+ */
+public class HttpWriter extends Writer
+{
+    public static final int MAX_OUTPUT_CHARS = 512; 
+    
+    private static final int WRITE_CONV = 0;
+    private static final int WRITE_ISO1 = 1;
+    private static final int WRITE_UTF8 = 2;
+    
+    final HttpOutput _out;
+    final AbstractGenerator _generator;
+    int _writeMode;
+    int _surrogate;
+
+    /* ------------------------------------------------------------ */
+    public HttpWriter(HttpOutput out)
+    {
+        _out=out;
+        _generator=_out._generator;
+        _surrogate=0; // AS lastUTF16CodePoint
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setCharacterEncoding(String encoding)
+    {
+        if (encoding == null || StringUtil.__ISO_8859_1.equalsIgnoreCase(encoding))
+        {
+            _writeMode = WRITE_ISO1;
+        }
+        else if (StringUtil.__UTF8.equalsIgnoreCase(encoding))
+        {
+            _writeMode = WRITE_UTF8;
+        }
+        else
+        {
+            _writeMode = WRITE_CONV;
+            if (_out._characterEncoding == null || !_out._characterEncoding.equalsIgnoreCase(encoding))
+                _out._converter = null; // Set lazily in getConverter()
+        }
+        
+        _out._characterEncoding = encoding;
+        if (_out._bytes==null)
+            _out._bytes = new ByteArrayOutputStream2(MAX_OUTPUT_CHARS);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close() throws IOException
+    {
+        _out.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush() throws IOException
+    {
+        _out.flush();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (String s,int offset, int length) throws IOException
+    {   
+        while (length > MAX_OUTPUT_CHARS)
+        {
+            write(s, offset, MAX_OUTPUT_CHARS);
+            offset += MAX_OUTPUT_CHARS;
+            length -= MAX_OUTPUT_CHARS;
+        }
+
+        if (_out._chars==null)
+        {
+            _out._chars = new char[MAX_OUTPUT_CHARS]; 
+        }
+        char[] chars = _out._chars;
+        s.getChars(offset, offset + length, chars, 0);
+        write(chars, 0, length);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (char[] s,int offset, int length) throws IOException
+    {              
+        HttpOutput out = _out; 
+        
+        while (length > 0)
+        {  
+            out._bytes.reset();
+            int chars = length>MAX_OUTPUT_CHARS?MAX_OUTPUT_CHARS:length;
+            
+            switch (_writeMode)
+            {
+                case WRITE_CONV:
+                {
+                    Writer converter=getConverter();
+                    converter.write(s, offset, chars);
+                    converter.flush();
+                }
+                break;
+
+                case WRITE_ISO1:
+                {
+                    byte[] buffer=out._bytes.getBuf();
+                    int bytes=out._bytes.getCount();
+                    
+                    if (chars>buffer.length-bytes)
+                        chars=buffer.length-bytes;
+
+                    for (int i = 0; i < chars; i++)
+                    {
+                        int c = s[offset+i];
+                        buffer[bytes++]=(byte)(c<256?c:'?'); // ISO-1 and UTF-8 match for 0 - 255
+                    }
+                    if (bytes>=0)
+                        out._bytes.setCount(bytes);
+
+                    break;
+                }
+
+                case WRITE_UTF8:
+                {
+                    byte[] buffer=out._bytes.getBuf();
+                    int bytes=out._bytes.getCount();
+
+                    if (bytes+chars>buffer.length)
+                        chars=buffer.length-bytes;
+
+                    for (int i = 0; i < chars; i++)
+                    {
+                        int code = s[offset+i];
+
+                        // Do we already have a surrogate?
+                        if(_surrogate==0)
+                        {
+                            // No - is this char code a surrogate?
+                            if(Character.isHighSurrogate((char)code))
+                            {
+                                _surrogate=code; // UCS-?
+                                continue;
+                            }                            
+                        }
+                        // else handle a low surrogate
+                        else if(Character.isLowSurrogate((char)code))
+                        {
+                            code = Character.toCodePoint((char)_surrogate, (char)code); // UCS-4
+                        }
+                        // else UCS-2
+                        else
+                        {
+                            code=_surrogate; // UCS-2
+                            _surrogate=0; // USED
+                            i--;
+                        }
+
+                        if ((code & 0xffffff80) == 0) 
+                        {
+                            // 1b
+                            if (bytes>=buffer.length)
+                            {
+                                chars=i;
+                                break;
+                            }
+                            buffer[bytes++]=(byte)(code);
+                        }
+                        else
+                        {
+                            if((code&0xfffff800)==0)
+                            {
+                                // 2b
+                                if (bytes+2>buffer.length)
+                                {
+                                    chars=i;
+                                    break;
+                                }
+                                buffer[bytes++]=(byte)(0xc0|(code>>6));
+                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                            }
+                            else if((code&0xffff0000)==0)
+                            {
+                                // 3b
+                                if (bytes+3>buffer.length)
+                                {
+                                    chars=i;
+                                    break;
+                                }
+                                buffer[bytes++]=(byte)(0xe0|(code>>12));
+                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                            }
+                            else if((code&0xff200000)==0)
+                            {
+                                // 4b
+                                if (bytes+4>buffer.length)
+                                {
+                                    chars=i;
+                                    break;
+                                }
+                                buffer[bytes++]=(byte)(0xf0|(code>>18));
+                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                            }
+                            else if((code&0xf4000000)==0)
+                            {
+                                // 5b
+                                if (bytes+5>buffer.length)
+                                {
+                                    chars=i;
+                                    break;
+                                }
+                                buffer[bytes++]=(byte)(0xf8|(code>>24));
+                                buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                            }
+                            else if((code&0x80000000)==0)
+                            {
+                                // 6b
+                                if (bytes+6>buffer.length)
+                                {
+                                    chars=i;
+                                    break;
+                                }
+                                buffer[bytes++]=(byte)(0xfc|(code>>30));
+                                buffer[bytes++]=(byte)(0x80|((code>>24)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>18)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>12)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|((code>>6)&0x3f));
+                                buffer[bytes++]=(byte)(0x80|(code&0x3f));
+                            }
+                            else
+                            {
+                                buffer[bytes++]=(byte)('?');
+                            } 
+
+                            _surrogate=0; // USED
+
+                            if (bytes==buffer.length)
+                            {
+                                chars=i+1;
+                                break;
+                            }
+                        }
+                    }
+                    out._bytes.setCount(bytes);
+                    break;
+                }
+                default:
+                    throw new IllegalStateException();
+            }
+            
+            out._bytes.writeTo(out);
+            length-=chars;
+            offset+=chars;
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private Writer getConverter() throws IOException
+    {
+        if (_out._converter == null)
+            _out._converter = new OutputStreamWriter(_out._bytes, _out._characterEncoding);
+        return _out._converter;
+    }   
+}
diff --git a/src/java/org/eclipse/jetty/server/InclusiveByteRange.java b/src/java/org/eclipse/jetty/server/InclusiveByteRange.java
new file mode 100644
index 0000000..10329bb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/InclusiveByteRange.java
@@ -0,0 +1,227 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Byte range inclusive of end points.
+ * <PRE>
+ * 
+ *   parses the following types of byte ranges:
+ * 
+ *       bytes=100-499
+ *       bytes=-300
+ *       bytes=100-
+ *       bytes=1-2,2-3,6-,-2
+ *
+ *   given an entity length, converts range to string
+ * 
+ *       bytes 100-499/500
+ * 
+ * </PRE>
+ * 
+ * Based on RFC2616 3.12, 14.16, 14.35.1, 14.35.2
+ * @version $version$
+ * 
+ */
+public class InclusiveByteRange 
+{
+    private static final Logger LOG = Log.getLogger(InclusiveByteRange.class);
+
+    long first = 0;
+    long last  = 0;    
+
+    public InclusiveByteRange(long first, long last)
+    {
+        this.first = first;
+        this.last = last;
+    }
+    
+    public long getFirst()
+    {
+        return first;
+    }
+
+    public long getLast()
+    {
+        return last;
+    }    
+
+
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param headers Enumeration of Range header fields.
+     * @param size Size of the resource.
+     * @return LazyList of satisfiable ranges
+     */
+    public static List satisfiableRanges(Enumeration headers, long size)
+    {
+        Object satRanges=null;
+        
+        // walk through all Range headers
+    headers:
+        while (headers.hasMoreElements())
+        {
+            String header = (String) headers.nextElement();
+            StringTokenizer tok = new StringTokenizer(header,"=,",false);
+            String t=null;
+            try
+            {
+                // read all byte ranges for this header 
+                while (tok.hasMoreTokens())
+                {
+                    try
+                    {
+                        t = tok.nextToken().trim();
+
+                        long first = -1;
+                        long last = -1;
+                        int d = t.indexOf('-');
+                        if (d < 0 || t.indexOf("-",d + 1) >= 0)
+                        {
+                            if ("bytes".equals(t))
+                                continue;
+                            LOG.warn("Bad range format: {}",t);
+                            continue headers;
+                        }
+                        else if (d == 0)
+                        {
+                            if (d + 1 < t.length())
+                                last = Long.parseLong(t.substring(d + 1).trim());
+                            else
+                            {
+                                LOG.warn("Bad range format: {}",t);
+                                continue;
+                            }
+                        }
+                        else if (d + 1 < t.length())
+                        {
+                            first = Long.parseLong(t.substring(0,d).trim());
+                            last = Long.parseLong(t.substring(d + 1).trim());
+                        }
+                        else
+                            first = Long.parseLong(t.substring(0,d).trim());
+
+                        if (first == -1 && last == -1)
+                            continue headers;
+
+                        if (first != -1 && last != -1 && (first > last))
+                            continue headers;
+
+                        if (first < size)
+                        {
+                            InclusiveByteRange range = new InclusiveByteRange(first,last);
+                            satRanges = LazyList.add(satRanges,range);
+                        }
+                    }
+                    catch (NumberFormatException e)
+                    {
+                        LOG.warn("Bad range format: {}",t);
+                        LOG.ignore(e);
+                        continue;
+                    }
+                }
+            }
+            catch(Exception e)
+            {
+                LOG.warn("Bad range format: {}",t);
+                LOG.ignore(e);
+            }    
+        }
+        return LazyList.getList(satRanges,true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getFirst(long size)
+    {
+        if (first<0)
+        {
+            long tf=size-last;
+            if (tf<0)
+                tf=0;
+            return tf;
+        }
+        return first;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getLast(long size)
+    {
+        if (first<0)
+            return size-1;
+        
+        if (last<0 ||last>=size)
+            return size-1;
+        return last;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getSize(long size)
+    {
+        return getLast(size)-getFirst(size)+1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String toHeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes ");
+        sb.append(getFirst(size));
+        sb.append('-');
+        sb.append(getLast(size));
+        sb.append("/");
+        sb.append(size);
+        return sb.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String to416HeaderRangeString(long size)
+    {
+        StringBuilder sb = new StringBuilder(40);
+        sb.append("bytes */");
+        sb.append(size);
+        return sb.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuilder sb = new StringBuilder(60);
+        sb.append(Long.toString(first));
+        sb.append(":");
+        sb.append(Long.toString(last));
+        return sb.toString();
+    }
+
+
+}
+
+
+
diff --git a/src/java/org/eclipse/jetty/server/LocalConnector.java b/src/java/org/eclipse/jetty/server/LocalConnector.java
new file mode 100644
index 0000000..e7edad3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/LocalConnector.java
@@ -0,0 +1,176 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.ByteArrayEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class LocalConnector extends AbstractConnector
+{
+    private static final Logger LOG = Log.getLogger(LocalConnector.class);
+    private final BlockingQueue<Request> _requests = new LinkedBlockingQueue<Request>();
+    
+    public LocalConnector()
+    {
+        setMaxIdleTime(30000);
+    }
+
+    public Object getConnection()
+    {
+        return this;
+    }
+
+    public String getResponses(String requests) throws Exception
+    {
+        return getResponses(requests, false);
+    }
+
+    public String getResponses(String requests, boolean keepOpen) throws Exception
+    {
+        ByteArrayBuffer result = getResponses(new ByteArrayBuffer(requests, StringUtil.__ISO_8859_1), keepOpen);
+        return result==null?null:result.toString(StringUtil.__ISO_8859_1);
+    }
+
+    public ByteArrayBuffer getResponses(ByteArrayBuffer requestsBuffer, boolean keepOpen) throws Exception
+    {
+        CountDownLatch latch = new CountDownLatch(1);
+        Request request = new Request(requestsBuffer, keepOpen, latch);
+        _requests.add(request);
+        latch.await(getMaxIdleTime(),TimeUnit.MILLISECONDS);
+        return request.getResponsesBuffer();
+    }
+
+    @Override
+    protected void accept(int acceptorID) throws IOException, InterruptedException
+    {
+        Request request = _requests.take();
+        getThreadPool().dispatch(request);
+    }
+
+    public void open() throws IOException
+    {
+    }
+
+    public void close() throws IOException
+    {
+    }
+
+    public int getLocalPort()
+    {
+        return -1;
+    }
+
+    public void executeRequest(String rawRequest) throws IOException
+    {
+        Request request = new Request(new ByteArrayBuffer(rawRequest, "UTF-8"), true, null);
+        _requests.add(request);
+    }
+
+    private class Request implements Runnable
+    {
+        private final ByteArrayBuffer _requestsBuffer;
+        private final boolean _keepOpen;
+        private final CountDownLatch _latch;
+        private volatile ByteArrayBuffer _responsesBuffer;
+
+        private Request(ByteArrayBuffer requestsBuffer, boolean keepOpen, CountDownLatch latch)
+        {
+            _requestsBuffer = requestsBuffer;
+            _keepOpen = keepOpen;
+            _latch = latch;
+        }
+
+        public void run()
+        {
+            try
+            {
+                ByteArrayEndPoint endPoint = new ByteArrayEndPoint(_requestsBuffer.asArray(), 1024)
+                {
+                    @Override
+                    public void setConnection(Connection connection)
+                    {
+                        if (getConnection()!=null && connection!=getConnection())
+                            connectionUpgraded(getConnection(),connection);
+                        super.setConnection(connection);
+                    }
+                };
+
+                endPoint.setGrowOutput(true);
+                AbstractHttpConnection connection = new BlockingHttpConnection(LocalConnector.this, endPoint, getServer());
+                endPoint.setConnection(connection);
+                connectionOpened(connection);
+
+                boolean leaveOpen = _keepOpen;
+                try
+                {
+                    while (endPoint.getIn().length() > 0 && endPoint.isOpen())
+                    {
+                        while (true)
+                        {
+                            final Connection con = endPoint.getConnection();
+                            final Connection next = con.handle();
+                            if (next!=con)
+                            {  
+                                endPoint.setConnection(next);
+                                continue;
+                            }
+                            break;
+                        }
+                    }
+                }
+                catch (IOException x)
+                {
+                    LOG.debug(x);
+                    leaveOpen = false;
+                }
+                catch (Exception x)
+                {
+                    LOG.warn(x);
+                    leaveOpen = false;
+                }
+                finally
+                {
+                    if (!leaveOpen)
+                        connectionClosed(connection);
+                    _responsesBuffer = endPoint.getOut();
+                }
+            }
+            finally
+            {
+                if (_latch != null)
+                    _latch.countDown();
+            }
+        }
+
+        public ByteArrayBuffer getResponsesBuffer()
+        {
+            return _responsesBuffer;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/NCSARequestLog.java b/src/java/org/eclipse/jetty/server/NCSARequestLog.java
new file mode 100644
index 0000000..5bcf73d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/NCSARequestLog.java
@@ -0,0 +1,726 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * This {@link RequestLog} implementation outputs logs in the pseudo-standard
+ * NCSA common log format. Configuration options allow a choice between the
+ * standard Common Log Format (as used in the 3 log format) and the Combined Log
+ * Format (single log format). This log format can be output by most web
+ * servers, and almost all web log analysis software can understand these
+ * formats.
+ *
+ * @org.apache.xbean.XBean element="ncsaLog"
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class NCSARequestLog extends AbstractLifeCycle implements RequestLog
+{
+    private static final Logger LOG = Log.getLogger(NCSARequestLog.class);
+    private static ThreadLocal<StringBuilder> _buffers = new ThreadLocal<StringBuilder>()
+            {
+                @Override
+                protected StringBuilder initialValue()
+                {
+                    return new StringBuilder(256);
+                }
+            };
+
+    private String _filename;
+    private boolean _extended;
+    private boolean _append;
+    private int _retainDays;
+    private boolean _closeOut;
+    private boolean _preferProxiedForAddress;
+    private String _logDateFormat = "dd/MMM/yyyy:HH:mm:ss Z";
+    private String _filenameDateFormat = null;
+    private Locale _logLocale = Locale.getDefault();
+    private String _logTimeZone = "GMT";
+    private String[] _ignorePaths;
+    private boolean _logLatency = false;
+    private boolean _logCookies = false;
+    private boolean _logServer = false;
+    private boolean _logDispatch = false;
+
+    private transient OutputStream _out;
+    private transient OutputStream _fileOut;
+    private transient DateCache _logDateCache;
+    private transient PathMap _ignorePathMap;
+    private transient Writer _writer;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with default settings.
+     */
+    public NCSARequestLog()
+    {
+        _extended = true;
+        _append = true;
+        _retainDays = 31;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create request log object with specified output file name.
+     * 
+     * @param filename the file name for the request log.
+     *                 This may be in the format expected
+     *                 by {@link RolloverFileOutputStream}
+     */
+    public NCSARequestLog(String filename)
+    {
+        _extended = true;
+        _append = true;
+        _retainDays = 31;
+        setFilename(filename);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the output file name of the request log.
+     * The file name may be in the format expected by
+     * {@link RolloverFileOutputStream}.
+     * 
+     * @param filename file name of the request log
+     *                
+     */
+    public void setFilename(String filename)
+    {
+        if (filename != null)
+        {
+            filename = filename.trim();
+            if (filename.length() == 0)
+                filename = null;
+        }
+        _filename = filename;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the output file name of the request log.
+     * 
+     * @return file name of the request log
+     */
+    public String getFilename()
+    {
+        return _filename;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name of the request log with the expanded
+     * date wildcard if the output is written to the disk using
+     * {@link RolloverFileOutputStream}.
+     * 
+     * @return file name of the request log, or null if not applicable
+     */
+    public String getDatedFilename()
+    {
+        if (_fileOut instanceof RolloverFileOutputStream)
+            return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the timestamp format for request log entries in the file.
+     * If this is not set, the pre-formated request timestamp is used.
+     * 
+     * @param format timestamp format string 
+     */
+    public void setLogDateFormat(String format)
+    {
+        _logDateFormat = format;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the timestamp format string for request log entries.
+     * 
+     * @return timestamp format string.
+     */
+    public String getLogDateFormat()
+    {
+        return _logDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the locale of the request log.
+     * 
+     * @param logLocale locale object
+     */
+    public void setLogLocale(Locale logLocale)
+    {
+        _logLocale = logLocale;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the locale of the request log.
+     * 
+     * @return locale object
+     */
+    public Locale getLogLocale()
+    {
+        return _logLocale;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the timezone of the request log.
+     * 
+     * @param tz timezone string
+     */
+    public void setLogTimeZone(String tz)
+    {
+        _logTimeZone = tz;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the timezone of the request log.
+     * 
+     * @return timezone string
+     */
+    public String getLogTimeZone()
+    {
+        return _logTimeZone;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the number of days before rotated log files are deleted.
+     * 
+     * @param retainDays number of days to keep a log file
+     */
+    public void setRetainDays(int retainDays)
+    {
+        _retainDays = retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the number of days before rotated log files are deleted.
+     * 
+     * @return number of days to keep a log file
+     */
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the extended request log format flag.
+     * 
+     * @param extended true - log the extended request information,
+     *                 false - do not log the extended request information
+     */
+    public void setExtended(boolean extended)
+    {
+        _extended = extended;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the extended request log format flag.
+     * 
+     * @return value of the flag
+     */
+    public boolean isExtended()
+    {
+        return _extended;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set append to log flag.
+     * 
+     * @param append true - request log file will be appended after restart,
+     *               false - request log file will be overwritten after restart
+     */
+    public void setAppend(boolean append)
+    {
+        _append = append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve append to log flag.
+     * 
+     * @return value of the flag
+     */
+    public boolean isAppend()
+    {
+        return _append;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set request paths that will not be logged.
+     * 
+     * @param ignorePaths array of request paths
+     */
+    public void setIgnorePaths(String[] ignorePaths)
+    {
+        _ignorePaths = ignorePaths;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the request paths that will not be logged.
+     * 
+     * @return array of request paths
+     */
+    public String[] getIgnorePaths()
+    {
+        return _ignorePaths;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Controls logging of the request cookies.
+     * 
+     * @param logCookies true - values of request cookies will be logged,
+     *                   false - values of request cookies will not be logged
+     */
+    public void setLogCookies(boolean logCookies)
+    {
+        _logCookies = logCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve log cookies flag
+     * 
+     * @return value of the flag
+     */
+    public boolean getLogCookies()
+    {
+        return _logCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Controls logging of the request hostname.
+     * 
+     * @param logServer true - request hostname will be logged,
+     *                  false - request hostname will not be logged
+     */
+    public void setLogServer(boolean logServer)
+    {
+        _logServer = logServer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve log hostname flag.
+     * 
+     * @return value of the flag
+     */
+    public boolean getLogServer()
+    {
+        return _logServer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Controls logging of request processing time.
+     * 
+     * @param logLatency true - request processing time will be logged
+     *                   false - request processing time will not be logged
+     */
+    public void setLogLatency(boolean logLatency)
+    {
+        _logLatency = logLatency;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve log request processing time flag.
+     * 
+     * @return value of the flag
+     */
+    public boolean getLogLatency()
+    {
+        return _logLatency;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Controls whether the actual IP address of the connection or
+     * the IP address from the X-Forwarded-For header will be logged.
+     * 
+     * @param preferProxiedForAddress true - IP address from header will be logged,
+     *                                false - IP address from the connection will be logged
+     */
+    public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
+    {
+        _preferProxiedForAddress = preferProxiedForAddress;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieved log X-Forwarded-For IP address flag.
+     * 
+     * @return value of the flag
+     */
+    public boolean getPreferProxiedForAddress()
+    {
+        return _preferProxiedForAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the log file name date format.
+     * @see RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)
+     * 
+     * @param logFileDateFormat format string that is passed to {@link RolloverFileOutputStream}
+     */
+    public void setFilenameDateFormat(String logFileDateFormat)
+    {
+        _filenameDateFormat = logFileDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the file name date format string.
+     * 
+     * @return the log File Date Format
+     */
+    public String getFilenameDateFormat()
+    {
+        return _filenameDateFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Controls logging of the request dispatch time
+     * 
+     * @param value true - request dispatch time will be logged
+     *              false - request dispatch time will not be logged
+     */
+    public void setLogDispatch(boolean value)
+    {
+        _logDispatch = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve request dispatch time logging flag
+     * 
+     * @return value of the flag
+     */
+    public boolean isLogDispatch()
+    {
+        return _logDispatch;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Writes the request and response information to the output stream.
+     * 
+     * @see org.eclipse.jetty.server.RequestLog#log(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response)
+     */
+    public void log(Request request, Response response)
+    {
+        try
+        {
+            if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
+                return;
+
+            if (_fileOut == null)
+                return;
+
+            StringBuilder buf= _buffers.get();
+            buf.setLength(0);
+
+            if (_logServer)
+            {
+                buf.append(request.getServerName());
+                buf.append(' ');
+            }
+
+            String addr = null;
+            if (_preferProxiedForAddress)
+            {
+                addr = request.getHeader(HttpHeaders.X_FORWARDED_FOR);
+            }
+
+            if (addr == null)
+                addr = request.getRemoteAddr();
+
+            buf.append(addr);
+            buf.append(" - ");
+            Authentication authentication=request.getAuthentication();
+            if (authentication instanceof Authentication.User)
+                buf.append(((Authentication.User)authentication).getUserIdentity().getUserPrincipal().getName());
+            else
+                buf.append(" - ");
+
+            buf.append(" [");
+            if (_logDateCache != null)
+                buf.append(_logDateCache.format(request.getTimeStamp()));
+            else
+                buf.append(request.getTimeStampBuffer().toString());
+
+            buf.append("] \"");
+            buf.append(request.getMethod());
+            buf.append(' ');
+            buf.append(request.getUri().toString());
+            buf.append(' ');
+            buf.append(request.getProtocol());
+            buf.append("\" ");
+            if (request.getAsyncContinuation().isInitial())
+            {
+                int status = response.getStatus();
+                if (status <= 0)
+                    status = 404;
+                buf.append((char)('0' + ((status / 100) % 10)));
+                buf.append((char)('0' + ((status / 10) % 10)));
+                buf.append((char)('0' + (status % 10)));
+            }
+            else
+                buf.append("Async");
+
+            long responseLength = response.getContentCount();
+            if (responseLength >= 0)
+            {
+                buf.append(' ');
+                if (responseLength > 99999)
+                    buf.append(responseLength);
+                else
+                {
+                    if (responseLength > 9999)
+                        buf.append((char)('0' + ((responseLength / 10000) % 10)));
+                    if (responseLength > 999)
+                        buf.append((char)('0' + ((responseLength / 1000) % 10)));
+                    if (responseLength > 99)
+                        buf.append((char)('0' + ((responseLength / 100) % 10)));
+                    if (responseLength > 9)
+                        buf.append((char)('0' + ((responseLength / 10) % 10)));
+                    buf.append((char)('0' + (responseLength) % 10));
+                }
+                buf.append(' ');
+            }
+            else
+                buf.append(" - ");
+
+            
+            if (_extended)
+                logExtended(request, response, buf);
+
+            if (_logCookies)
+            {
+                Cookie[] cookies = request.getCookies();
+                if (cookies == null || cookies.length == 0)
+                    buf.append(" -");
+                else
+                {
+                    buf.append(" \"");
+                    for (int i = 0; i < cookies.length; i++)
+                    {
+                        if (i != 0)
+                            buf.append(';');
+                        buf.append(cookies[i].getName());
+                        buf.append('=');
+                        buf.append(cookies[i].getValue());
+                    }
+                    buf.append('\"');
+                }
+            }
+
+            if (_logDispatch || _logLatency)
+            {
+                long now = System.currentTimeMillis();
+
+                if (_logDispatch)
+                {   
+                    long d = request.getDispatchTime();
+                    buf.append(' ');
+                    buf.append(now - (d==0 ? request.getTimeStamp():d));
+                }
+
+                if (_logLatency)
+                {
+                    buf.append(' ');
+                    buf.append(now - request.getTimeStamp());
+                }
+            }
+
+            buf.append(StringUtil.__LINE_SEPARATOR);
+            
+            String log = buf.toString();
+            write(log);
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void write(String log) throws IOException 
+    {
+        synchronized(this)
+        {
+            if (_writer==null)
+                return;
+            _writer.write(log);
+            _writer.flush();
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Writes extended request and response information to the output stream.
+     * 
+     * @param request request object
+     * @param response response object
+     * @param b StringBuilder to write to
+     * @throws IOException
+     */
+    protected void logExtended(Request request,
+                               Response response,
+                               StringBuilder b) throws IOException
+    {
+        String referer = request.getHeader(HttpHeaders.REFERER);
+        if (referer == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(referer);
+            b.append("\" ");
+        }
+
+        String agent = request.getHeader(HttpHeaders.USER_AGENT);
+        if (agent == null)
+            b.append("\"-\" ");
+        else
+        {
+            b.append('"');
+            b.append(agent);
+            b.append('"');
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up request logging and open log file.
+     * 
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected synchronized void doStart() throws Exception
+    {
+        if (_logDateFormat != null)
+        {
+            _logDateCache = new DateCache(_logDateFormat,_logLocale);
+            _logDateCache.setTimeZoneID(_logTimeZone);
+        }
+
+        if (_filename != null)
+        {
+            _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,TimeZone.getTimeZone(_logTimeZone),_filenameDateFormat,null);
+            _closeOut = true;
+            LOG.info("Opened " + getDatedFilename());
+        }
+        else
+            _fileOut = System.err;
+
+        _out = _fileOut;
+
+        if (_ignorePaths != null && _ignorePaths.length > 0)
+        {
+            _ignorePathMap = new PathMap();
+            for (int i = 0; i < _ignorePaths.length; i++)
+                _ignorePathMap.put(_ignorePaths[i],_ignorePaths[i]);
+        }
+        else
+            _ignorePathMap = null;
+
+        synchronized(this)
+        {
+            _writer = new OutputStreamWriter(_out);
+        }
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Close the log file and perform cleanup.
+     * 
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        synchronized (this)
+        {
+            super.doStop();
+            try
+            {
+                if (_writer != null)
+                    _writer.flush();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+            if (_out != null && _closeOut)
+                try
+                {
+                    _out.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.ignore(e);
+                }
+
+            _out = null;
+            _fileOut = null;
+            _closeOut = false;
+            _logDateCache = null;
+            _writer = null;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/Request.java b/src/java/org/eclipse/jetty/server/Request.java
new file mode 100644
index 0000000..4b8c1d4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Request.java
@@ -0,0 +1,2204 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestAttributeEvent;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.BufferUtil;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.io.nio.NIOBuffer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty Request.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletRequest} from the <code>javax.servlet.http</code> package.
+ * </p>
+ * <p>
+ * The standard interface of mostly getters, is extended with setters so that the request is mutable by the handlers that it is passed to. This allows the
+ * request object to be as lightweight as possible and not actually implement any significant behavior. For example
+ * <ul>
+ *
+ * <li>The {@link Request#getContextPath()} method will return null, until the request has been passed to a {@link ContextHandler} which matches the
+ * {@link Request#getPathInfo()} with a context path and calls {@link Request#setContextPath(String)} as a result.</li>
+ *
+ * <li>the HTTP session methods will all return null sessions until such time as a request has been passed to a
+ * {@link org.eclipse.jetty.server.session.SessionHandler} which checks for session cookies and enables the ability to create new sessions.</li>
+ *
+ * <li>The {@link Request#getServletPath()} method will return null until the request has been passed to a <code>org.eclipse.jetty.servlet.ServletHandler</code>
+ * and the pathInfo matched against the servlet URL patterns and {@link Request#setServletPath(String)} called as a result.</li>
+ * </ul>
+ *
+ * A request instance is created for each {@link AbstractHttpConnection} accepted by the server and recycled for each HTTP request received via that connection.
+ * An effort is made to avoid reparsing headers and cookies that are likely to be the same for requests from the same connection.
+ *
+ * <p>
+ * The form content that a request can process is limited to protect from Denial of Service attacks. The size in bytes is limited by
+ * {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
+ * attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
+ * "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
+ *
+ *
+ */
+public class Request implements HttpServletRequest
+{
+    public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
+    public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
+    public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
+    private static final Logger LOG = Log.getLogger(Request.class);
+
+    private static final String __ASYNC_FWD = "org.eclipse.asyncfwd";
+    private static final Collection __defaultLocale = Collections.singleton(Locale.getDefault());
+    private static final int __NONE = 0, _STREAM = 1, __READER = 2;
+
+    public static class MultiPartCleanerListener implements ServletRequestListener
+    {
+
+        @Override
+        public void requestDestroyed(ServletRequestEvent sre)
+        {
+            //Clean up any tmp files created by MultiPartInputStream
+            MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
+            if (mpis != null)
+            {
+                ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
+
+                //Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
+                if (context == sre.getServletContext())
+                {
+                    try
+                    {
+                        mpis.deleteParts();
+                    }
+                    catch (MultiException e)
+                    {
+                        sre.getServletContext().log("Errors deleting multipart tmp files", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void requestInitialized(ServletRequestEvent sre)
+        {
+            //nothing to do, multipart config set up by ServletHolder.handle()
+        }
+        
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static Request getRequest(HttpServletRequest request)
+    {
+        if (request instanceof Request)
+            return (Request)request;
+
+        return AbstractHttpConnection.getCurrentConnection().getRequest();
+    }
+    protected final AsyncContinuation _async = new AsyncContinuation();
+    private boolean _asyncSupported = true;
+    private volatile Attributes _attributes;
+    private Authentication _authentication;
+    private MultiMap<String> _baseParameters;
+    private String _characterEncoding;
+    protected AbstractHttpConnection _connection;
+    private ContextHandler.Context _context;
+    private boolean _newContext;
+    private String _contextPath;
+    private CookieCutter _cookies;
+    private boolean _cookiesExtracted = false;
+    private DispatcherType _dispatcherType;
+    private boolean _dns = false;
+    private EndPoint _endp;
+    private boolean _handled = false;
+    private int _inputState = __NONE;
+    private String _method;
+    private MultiMap<String> _parameters;
+    private boolean _paramsExtracted;
+    private String _pathInfo;
+    private int _port;
+    private String _protocol = HttpVersions.HTTP_1_1;
+    private String _queryEncoding;
+    private String _queryString;
+    private BufferedReader _reader;
+    private String _readerEncoding;
+    private String _remoteAddr;
+    private String _remoteHost;
+    private Object _requestAttributeListeners;
+    private String _requestedSessionId;
+    private boolean _requestedSessionIdFromCookie = false;
+    private String _requestURI;
+    private Map<Object, HttpSession> _savedNewSessions;
+    private String _scheme = URIUtil.HTTP;
+    private UserIdentity.Scope _scope;
+    private String _serverName;
+    private String _servletPath;
+    private HttpSession _session;
+    private SessionManager _sessionManager;
+    private long _timeStamp;
+    private long _dispatchTime;
+
+    private Buffer _timeStampBuffer;
+    private HttpURI _uri;
+    
+    private MultiPartInputStream _multiPartInputStream; //if the request is a multi-part mime
+    
+    /* ------------------------------------------------------------ */
+    public Request()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public Request(AbstractHttpConnection connection)
+    {
+        setConnection(connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addEventListener(final EventListener listener)
+    {
+        if (listener instanceof ServletRequestAttributeListener)
+            _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
+        if (listener instanceof ContinuationListener)
+            throw new IllegalArgumentException(listener.getClass().toString());
+        if (listener instanceof AsyncListener)
+            throw new IllegalArgumentException(listener.getClass().toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Extract Parameters from query string and/or form _content.
+     */
+    public void extractParameters()
+    {
+        if (_baseParameters == null)
+            _baseParameters = new MultiMap(16);
+
+        if (_paramsExtracted)
+        {
+            if (_parameters == null)
+                _parameters = _baseParameters;
+            return;
+        }
+
+        _paramsExtracted = true;
+
+        try
+        {
+            // Handle query string
+            if (_uri != null && _uri.hasQuery())
+            {
+                if (_queryEncoding == null)
+                    _uri.decodeQueryTo(_baseParameters);
+                else
+                {
+                    try
+                    {
+                        _uri.decodeQueryTo(_baseParameters,_queryEncoding);
+                    }
+                    catch (UnsupportedEncodingException e)
+                    {
+                        if (LOG.isDebugEnabled())
+                            LOG.warn(e);
+                        else
+                            LOG.warn(e.toString());
+                    }
+                }
+            }
+
+            // handle any _content.
+            String encoding = getCharacterEncoding();
+            String content_type = getContentType();
+            if (content_type != null && content_type.length() > 0)
+            {
+                content_type = HttpFields.valueParameters(content_type,null);
+
+                if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState == __NONE
+                        && (HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
+                {
+                    int content_length = getContentLength();
+                    if (content_length != 0)
+                    {
+                        try
+                        {
+                            int maxFormContentSize = -1;
+                            int maxFormKeys = -1;
+
+                            if (_context != null)
+                            {
+                                maxFormContentSize = _context.getContextHandler().getMaxFormContentSize();
+                                maxFormKeys = _context.getContextHandler().getMaxFormKeys();
+                            }
+                            
+                            if (maxFormContentSize < 0)
+                            {
+                                Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+                                if (obj == null)
+                                    maxFormContentSize = 200000;
+                                else if (obj instanceof Number)
+                                {                      
+                                    Number size = (Number)obj;
+                                    maxFormContentSize = size.intValue();
+                                }
+                                else if (obj instanceof String)
+                                {
+                                    maxFormContentSize = Integer.valueOf((String)obj);
+                                }
+                            }
+                            
+                            if (maxFormKeys < 0)
+                            {
+                                Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
+                                if (obj == null)
+                                    maxFormKeys = 1000;
+                                else if (obj instanceof Number)
+                                {
+                                    Number keys = (Number)obj;
+                                    maxFormKeys = keys.intValue();
+                                }
+                                else if (obj instanceof String)
+                                {
+                                    maxFormKeys = Integer.valueOf((String)obj);
+                                }
+                            }
+
+                            if (content_length > maxFormContentSize && maxFormContentSize > 0)
+                            {
+                                throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize);
+                            }
+                            InputStream in = getInputStream();
+
+                            // Add form params to query params
+                            UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys);
+                        }
+                        catch (IOException e)
+                        {
+                            if (LOG.isDebugEnabled())
+                                LOG.warn(e);
+                            else
+                                LOG.warn(e.toString());
+                        }
+                    }
+                }
+              
+            }
+
+            if (_parameters == null)
+                _parameters = _baseParameters;
+            else if (_parameters != _baseParameters)
+            {
+                // Merge parameters (needed if parameters extracted after a forward).
+                Iterator iter = _baseParameters.entrySet().iterator();
+                while (iter.hasNext())
+                {
+                    Map.Entry entry = (Map.Entry)iter.next();
+                    String name = (String)entry.getKey();
+                    Object values = entry.getValue();
+                    for (int i = 0; i < LazyList.size(values); i++)
+                        _parameters.add(name,LazyList.get(values,i));
+                }
+            }
+
+            if (content_type != null && content_type.length()>0 && content_type.startsWith("multipart/form-data") && getAttribute(__MULTIPART_CONFIG_ELEMENT)!=null)
+            {
+                try
+                {
+                    getParts();
+                }
+                catch (IOException e)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.warn(e);
+                    else
+                        LOG.warn(e.toString());
+                }
+                catch (ServletException e)
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.warn(e);
+                    else
+                        LOG.warn(e.toString());
+                }
+            }
+        }
+        finally
+        {
+            // ensure params always set (even if empty) after extraction
+            if (_parameters == null)
+                _parameters = _baseParameters;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public AsyncContext getAsyncContext()
+    {
+        if (_async.isInitial() && !_async.isAsyncStarted())
+            throw new IllegalStateException(_async.getStatusString());
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    public AsyncContinuation getAsyncContinuation()
+    {
+        return _async;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
+            return new Long(getConnection().getEndPoint().getMaxIdleTime());
+
+        Object attr = (_attributes == null)?null:_attributes.getAttribute(name);
+        if (attr == null && Continuation.ATTRIBUTE.equals(name))
+            return _async;
+        return attr;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getAttributeNames()
+     */
+    public Enumeration getAttributeNames()
+    {
+        if (_attributes == null)
+            return Collections.enumeration(Collections.EMPTY_LIST);
+
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Attributes getAttributes()
+    {
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the authentication.
+     *
+     * @return the authentication
+     */
+    public Authentication getAuthentication()
+    {
+        return _authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getAuthType()
+     */
+    public String getAuthType()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+        
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getAuthMethod();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getCharacterEncoding()
+     */
+    public String getCharacterEncoding()
+    {
+        return _characterEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connection.
+     */
+    public AbstractHttpConnection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentLength()
+     */
+    public int getContentLength()
+    {
+        return (int)_connection.getRequestFields().getLongField(HttpHeaders.CONTENT_LENGTH_BUFFER);
+    }
+
+    public long getContentRead()
+    {
+        if (_connection == null || _connection.getParser() == null)
+            return -1;
+
+        return ((HttpParser)_connection.getParser()).getContentRead();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    public String getContentType()
+    {
+        return _connection.getRequestFields().getStringField(HttpHeaders.CONTENT_TYPE_BUFFER);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The current {@link Context context} used for this request, or <code>null</code> if {@link #setContext} has not yet been called.
+     */
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getContextPath()
+     */
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getCookies()
+     */
+    public Cookie[] getCookies()
+    {
+        if (_cookiesExtracted)
+            return _cookies == null?null:_cookies.getCookies();
+
+        _cookiesExtracted = true;
+
+        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.COOKIE_BUFFER);
+
+        // Handle no cookies
+        if (enm != null)
+        {
+            if (_cookies == null)
+                _cookies = new CookieCutter();
+
+            while (enm.hasMoreElements())
+            {
+                String c = (String)enm.nextElement();
+                _cookies.addCookieField(c);
+            }
+        }
+
+        return _cookies == null?null:_cookies.getCookies();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
+     */
+    public long getDateHeader(String name)
+    {
+        return _connection.getRequestFields().getDateField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public DispatcherType getDispatcherType()
+    {
+        return _dispatcherType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
+     */
+    public String getHeader(String name)
+    {
+        return _connection.getRequestFields().getStringField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
+     */
+    public Enumeration getHeaderNames()
+    {
+        return _connection.getRequestFields().getFieldNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
+     */
+    public Enumeration getHeaders(String name)
+    {
+        Enumeration e = _connection.getRequestFields().getValues(name);
+        if (e == null)
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the inputState.
+     */
+    public int getInputState()
+    {
+        return _inputState;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getInputStream()
+     */
+    public ServletInputStream getInputStream() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != _STREAM)
+            throw new IllegalStateException("READER");
+        _inputState = _STREAM;
+        return _connection.getInputStream();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
+     */
+    public int getIntHeader(String name)
+    {
+        return (int)_connection.getRequestFields().getLongField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalAddr()
+     */
+    public String getLocalAddr()
+    {
+        return _endp == null?null:_endp.getLocalAddr();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocale()
+     */
+    public Locale getLocale()
+    {
+        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Locale.getDefault();
+
+        // sort the list in quality order
+        List acceptLanguage = HttpFields.qualityList(enm);
+        if (acceptLanguage.size() == 0)
+            return Locale.getDefault();
+
+        int size = acceptLanguage.size();
+
+        if (size > 0)
+        {
+            String language = (String)acceptLanguage.get(0);
+            language = HttpFields.valueParameters(language,null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0,dash).trim();
+            }
+            return new Locale(language,country);
+        }
+
+        return Locale.getDefault();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocales()
+     */
+    public Enumeration getLocales()
+    {
+
+        Enumeration enm = _connection.getRequestFields().getValues(HttpHeaders.ACCEPT_LANGUAGE,HttpFields.__separators);
+
+        // handle no locale
+        if (enm == null || !enm.hasMoreElements())
+            return Collections.enumeration(__defaultLocale);
+
+        // sort the list in quality order
+        List acceptLanguage = HttpFields.qualityList(enm);
+
+        if (acceptLanguage.size() == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        Object langs = null;
+        int size = acceptLanguage.size();
+
+        // convert to locals
+        for (int i = 0; i < size; i++)
+        {
+            String language = (String)acceptLanguage.get(i);
+            language = HttpFields.valueParameters(language,null);
+            String country = "";
+            int dash = language.indexOf('-');
+            if (dash > -1)
+            {
+                country = language.substring(dash + 1).trim();
+                language = language.substring(0,dash).trim();
+            }
+            langs = LazyList.ensureSize(langs,size);
+            langs = LazyList.add(langs,new Locale(language,country));
+        }
+
+        if (LazyList.size(langs) == 0)
+            return Collections.enumeration(__defaultLocale);
+
+        return Collections.enumeration(LazyList.getList(langs));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalName()
+     */
+    public String getLocalName()
+    {
+        if (_endp == null)
+            return null;
+        if (_dns)
+            return _endp.getLocalHost();
+
+        String local = _endp.getLocalAddr();
+        if (local != null && local.indexOf(':') >= 0)
+            local = "[" + local + "]";
+        return local;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getLocalPort()
+     */
+    public int getLocalPort()
+    {
+        return _endp == null?0:_endp.getLocalPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getMethod()
+     */
+    public String getMethod()
+    {
+        return _method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+     */
+    public String getParameter(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        return (String)_parameters.getValue(name,0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterMap()
+     */
+    public Map getParameterMap()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+
+        return Collections.unmodifiableMap(_parameters.toStringArrayMap());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterNames()
+     */
+    public Enumeration getParameterNames()
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        return Collections.enumeration(_parameters.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the parameters.
+     */
+    public MultiMap<String> getParameters()
+    {
+        return _parameters;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+     */
+    public String[] getParameterValues(String name)
+    {
+        if (!_paramsExtracted)
+            extractParameters();
+        List<Object> vals = _parameters.getValues(name);
+        if (vals == null)
+            return null;
+        return vals.toArray(new String[vals.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathInfo()
+     */
+    public String getPathInfo()
+    {
+        return _pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
+     */
+    public String getPathTranslated()
+    {
+        if (_pathInfo == null || _context == null)
+            return null;
+        return _context.getRealPath(_pathInfo);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getProtocol()
+     */
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getQueryEncoding()
+    {
+        return _queryEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getQueryString()
+     */
+    public String getQueryString()
+    {
+        if (_queryString == null && _uri != null)
+        {
+            if (_queryEncoding == null)
+                _queryString = _uri.getQuery();
+            else
+                _queryString = _uri.getQuery(_queryEncoding);
+        }
+        return _queryString;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getReader()
+     */
+    public BufferedReader getReader() throws IOException
+    {
+        if (_inputState != __NONE && _inputState != __READER)
+            throw new IllegalStateException("STREAMED");
+
+        if (_inputState == __READER)
+            return _reader;
+
+        String encoding = getCharacterEncoding();
+        if (encoding == null)
+            encoding = StringUtil.__ISO_8859_1;
+
+        if (_reader == null || !encoding.equalsIgnoreCase(_readerEncoding))
+        {
+            final ServletInputStream in = getInputStream();
+            _readerEncoding = encoding;
+            _reader = new BufferedReader(new InputStreamReader(in,encoding))
+            {
+                @Override
+                public void close() throws IOException
+                {
+                    in.close();
+                }
+            };
+        }
+        _inputState = __READER;
+        return _reader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
+     */
+    public String getRealPath(String path)
+    {
+        if (_context == null)
+            return null;
+        return _context.getRealPath(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteAddr()
+     */
+    public String getRemoteAddr()
+    {
+        if (_remoteAddr != null)
+            return _remoteAddr;
+        return _endp == null?null:_endp.getRemoteAddr();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemoteHost()
+     */
+    public String getRemoteHost()
+    {
+        if (_dns)
+        {
+            if (_remoteHost != null)
+            {
+                return _remoteHost;
+            }
+            return _endp == null?null:_endp.getRemoteHost();
+        }
+        return getRemoteAddr();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRemotePort()
+     */
+    public int getRemotePort()
+    {
+        return _endp == null?0:_endp.getRemotePort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
+     */
+    public String getRemoteUser()
+    {
+        Principal p = getUserPrincipal();
+        if (p == null)
+            return null;
+        return p.getName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
+     */
+    public RequestDispatcher getRequestDispatcher(String path)
+    {
+        if (path == null || _context == null)
+            return null;
+
+        // handle relative path
+        if (!path.startsWith("/"))
+        {
+            String relTo = URIUtil.addPaths(_servletPath,_pathInfo);
+            int slash = relTo.lastIndexOf("/");
+            if (slash > 1)
+                relTo = relTo.substring(0,slash + 1);
+            else
+                relTo = "/";
+            path = URIUtil.addPaths(relTo,path);
+        }
+
+        return _context.getRequestDispatcher(path);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
+     */
+    public String getRequestedSessionId()
+    {
+        return _requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURI()
+     */
+    public String getRequestURI()
+    {
+        if (_requestURI == null && _uri != null)
+            _requestURI = _uri.getPathAndParam();
+        return _requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getRequestURL()
+     */
+    public StringBuffer getRequestURL()
+    {
+        final StringBuffer url = new StringBuffer(48);
+        synchronized (url)
+        {
+            String scheme = getScheme();
+            int port = getServerPort();
+
+            url.append(scheme);
+            url.append("://");
+            url.append(getServerName());
+            if (_port > 0 && ((scheme.equalsIgnoreCase(URIUtil.HTTP) && port != 80) || (scheme.equalsIgnoreCase(URIUtil.HTTPS) && port != 443)))
+            {
+                url.append(':');
+                url.append(_port);
+            }
+
+            url.append(getRequestURI());
+            return url;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Response getResponse()
+    {
+        return _connection._response;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and, but it does not include a
+     * path.
+     * <p>
+     * Because this method returns a <code>StringBuffer</code>, not a string, you can modify the URL easily, for example, to append path and query parameters.
+     *
+     * This method is useful for creating redirect messages and for reporting errors.
+     *
+     * @return "scheme://host:port"
+     */
+    public StringBuilder getRootURL()
+    {
+        StringBuilder url = new StringBuilder(48);
+        String scheme = getScheme();
+        int port = getServerPort();
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+
+        if (port > 0 && ((scheme.equalsIgnoreCase("http") && port != 80) || (scheme.equalsIgnoreCase("https") && port != 443)))
+        {
+            url.append(':');
+            url.append(port);
+        }
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getScheme()
+     */
+    public String getScheme()
+    {
+        return _scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerName()
+     */
+    public String getServerName()
+    {
+        // Return already determined host
+        if (_serverName != null)
+            return _serverName;
+
+        if (_uri == null)
+            throw new IllegalStateException("No uri");
+
+        // Return host from absolute URI
+        _serverName = _uri.getHost();
+        _port = _uri.getPort();
+        if (_serverName != null)
+            return _serverName;
+
+        // Return host from header field
+        Buffer hostPort = _connection.getRequestFields().get(HttpHeaders.HOST_BUFFER);
+        if (hostPort != null)
+        {
+            loop: for (int i = hostPort.putIndex(); i-- > hostPort.getIndex();)
+            {
+                char ch = (char)(0xff & hostPort.peek(i));
+                switch (ch)
+                {
+                    case ']':
+                        break loop;
+
+                    case ':':
+                        _serverName = BufferUtil.to8859_1_String(hostPort.peek(hostPort.getIndex(),i - hostPort.getIndex()));
+                        try
+                        {
+                            _port = BufferUtil.toInt(hostPort.peek(i + 1,hostPort.putIndex() - i - 1));
+                        }
+                        catch (NumberFormatException e)
+                        {
+                            try
+                            {
+                                if (_connection != null)
+                                    _connection._generator.sendError(HttpStatus.BAD_REQUEST_400,"Bad Host header",null,true);
+                            }
+                            catch (IOException e1)
+                            {
+                                throw new RuntimeException(e1);
+                            }
+                        }
+                        return _serverName;
+                }
+            }
+            if (_serverName == null || _port < 0)
+            {
+                _serverName = BufferUtil.to8859_1_String(hostPort);
+                _port = 0;
+            }
+
+            return _serverName;
+        }
+
+        // Return host from connection
+        if (_connection != null)
+        {
+            _serverName = getLocalName();
+            _port = getLocalPort();
+            if (_serverName != null && !StringUtil.ALL_INTERFACES.equals(_serverName))
+                return _serverName;
+        }
+
+        // Return the local host
+        try
+        {
+            _serverName = InetAddress.getLocalHost().getHostAddress();
+        }
+        catch (java.net.UnknownHostException e)
+        {
+            LOG.ignore(e);
+        }
+        return _serverName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getServerPort()
+     */
+    public int getServerPort()
+    {
+        if (_port <= 0)
+        {
+            if (_serverName == null)
+                getServerName();
+
+            if (_port <= 0)
+            {
+                if (_serverName != null && _uri != null)
+                    _port = _uri.getPort();
+                else
+                    _port = _endp == null?0:_endp.getLocalPort();
+            }
+        }
+
+        if (_port <= 0)
+        {
+            if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
+                return 443;
+            return 80;
+        }
+        return _port;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContext getServletContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String getServletName()
+    {
+        if (_scope != null)
+            return _scope.getName();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getServletPath()
+     */
+    public String getServletPath()
+    {
+        if (_servletPath == null)
+            _servletPath = "";
+        return _servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletResponse getServletResponse()
+    {
+        return _connection.getResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession()
+     */
+    public HttpSession getSession()
+    {
+        return getSession(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
+     */
+    public HttpSession getSession(boolean create)
+    {
+        if (_session != null)
+        {
+            if (_sessionManager != null && !_sessionManager.isValid(_session))
+                _session = null;
+            else
+                return _session;
+        }
+
+        if (!create)
+            return null;
+
+        if (_sessionManager == null)
+            throw new IllegalStateException("No SessionManager");
+
+        _session = _sessionManager.newHttpSession(this);
+        HttpCookie cookie = _sessionManager.getSessionCookie(_session,getContextPath(),isSecure());
+        if (cookie != null)
+            _connection.getResponse().addCookie(cookie);
+
+        return _session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request TimeStamp
+     *
+     * @return The time that the request was received.
+     */
+    public long getTimeStamp()
+    {
+        return _timeStamp;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get Request TimeStamp
+     *
+     * @return The time that the request was received.
+     */
+    public Buffer getTimeStampBuffer()
+    {
+        if (_timeStampBuffer == null && _timeStamp > 0)
+            _timeStampBuffer = HttpFields.__dateCache.formatBuffer(_timeStamp);
+        return _timeStampBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the uri.
+     */
+    public HttpURI getUri()
+    {
+        return _uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity getUserIdentity()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The resolved user Identity, which may be null if the {@link Authentication} is not {@link Authentication.User} (eg.
+     *         {@link Authentication.Deferred}).
+     */
+    public UserIdentity getResolvedUserIdentity()
+    {
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).getUserIdentity();
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public UserIdentity.Scope getUserIdentityScope()
+    {
+        return _scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
+     */
+    public Principal getUserPrincipal()
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+        {
+            UserIdentity user = ((Authentication.User)_authentication).getUserIdentity();
+            return user.getUserPrincipal();
+        }
+        
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get timestamp of the request dispatch
+     *
+     * @return timestamp
+     */
+    public long getDispatchTime()
+    {
+        return _dispatchTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isHandled()
+    {
+        return _handled;
+    }
+
+    public boolean isAsyncStarted()
+    {
+       return _async.isAsyncStarted();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
+     */
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return _requestedSessionId != null && _requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
+     */
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
+     */
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return _requestedSessionId != null && !_requestedSessionIdFromCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
+     */
+    public boolean isRequestedSessionIdValid()
+    {
+        if (_requestedSessionId == null)
+            return false;
+
+        HttpSession session = getSession(false);
+        return (session != null && _sessionManager.getSessionIdManager().getClusterId(_requestedSessionId).equals(_sessionManager.getClusterId(session)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#isSecure()
+     */
+    public boolean isSecure()
+    {
+        return _connection.isConfidential(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
+     */
+    public boolean isUserInRole(String role)
+    {
+        if (_authentication instanceof Authentication.Deferred)
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this));
+
+        if (_authentication instanceof Authentication.User)
+            return ((Authentication.User)_authentication).isUserInRole(_scope,role);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpSession recoverNewSession(Object key)
+    {
+        if (_savedNewSessions == null)
+            return null;
+        return _savedNewSessions.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void recycle()
+    {
+        if (_inputState == __READER)
+        {
+            try
+            {
+                int r = _reader.read();
+                while (r != -1)
+                    r = _reader.read();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                _reader = null;
+            }
+        }
+
+        setAuthentication(Authentication.NOT_CHECKED);
+        _async.recycle();
+        _asyncSupported = true;
+        _handled = false;
+        if (_context != null)
+            throw new IllegalStateException("Request in context!");
+        if (_attributes != null)
+            _attributes.clearAttributes();
+        _characterEncoding = null;
+        _contextPath = null;
+        if (_cookies != null)
+            _cookies.reset();
+        _cookiesExtracted = false;
+        _context = null;
+        _serverName = null;
+        _method = null;
+        _pathInfo = null;
+        _port = 0;
+        _protocol = HttpVersions.HTTP_1_1;
+        _queryEncoding = null;
+        _queryString = null;
+        _requestedSessionId = null;
+        _requestedSessionIdFromCookie = false;
+        _session = null;
+        _sessionManager = null;
+        _requestURI = null;
+        _scope = null;
+        _scheme = URIUtil.HTTP;
+        _servletPath = null;
+        _timeStamp = 0;
+        _timeStampBuffer = null;
+        _uri = null;
+        if (_baseParameters != null)
+            _baseParameters.clear();
+        _parameters = null;
+        _paramsExtracted = false;
+        _inputState = __NONE;
+
+        if (_savedNewSessions != null)
+            _savedNewSessions.clear();
+        _savedNewSessions=null;
+        _multiPartInputStream = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if (_attributes != null)
+            _attributes.removeAttribute(name);
+
+        if (old_value != null)
+        {
+            if (_requestAttributeListeners != null)
+            {
+                final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value);
+                final int size = LazyList.size(_requestAttributeListeners);
+                for (int i = 0; i < size; i++)
+                {
+                    final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
+                    if (listener instanceof ServletRequestAttributeListener)
+                    {
+                        final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
+                        l.attributeRemoved(event);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeEventListener(final EventListener listener)
+    {
+        _requestAttributeListeners = LazyList.remove(_requestAttributeListeners,listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveNewSession(Object key, HttpSession session)
+    {
+        if (_savedNewSessions == null)
+            _savedNewSessions = new HashMap<Object, HttpSession>();
+        _savedNewSessions.put(key,session);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean supported)
+    {
+        _asyncSupported = supported;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a request attribute. if the attribute name is "org.eclipse.jetty.server.server.Request.queryEncoding" then the value is also passed in a call to
+     * {@link #setQueryEncoding}. <p> if the attribute name is "org.eclipse.jetty.server.server.ResponseBuffer", then the response buffer is flushed with @{link
+     * #flushResponseBuffer} <p> if the attribute name is "org.eclipse.jetty.io.EndPoint.maxIdleTime", then the value is passed to the associated {@link
+     * EndPoint#setMaxIdleTime}.
+     *
+     * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object value)
+    {
+        Object old_value = _attributes == null?null:_attributes.getAttribute(name);
+
+        if (name.startsWith("org.eclipse.jetty."))
+        {
+            if ("org.eclipse.jetty.server.Request.queryEncoding".equals(name))
+                setQueryEncoding(value == null?null:value.toString());
+            else if ("org.eclipse.jetty.server.sendContent".equals(name))
+            {
+                try
+                {
+                    ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendContent(value);
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+            else if ("org.eclipse.jetty.server.ResponseBuffer".equals(name))
+            {
+                try
+                {
+                    final ByteBuffer byteBuffer = (ByteBuffer)value;
+                    synchronized (byteBuffer)
+                    {
+                        NIOBuffer buffer = byteBuffer.isDirect()?new DirectNIOBuffer(byteBuffer,true):new IndirectNIOBuffer(byteBuffer,true);
+                        ((AbstractHttpConnection.Output)getServletResponse().getOutputStream()).sendResponse(buffer);
+                    }
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+            else if ("org.eclipse.jetty.io.EndPoint.maxIdleTime".equalsIgnoreCase(name))
+            {
+                try
+                {
+                    getConnection().getEndPoint().setMaxIdleTime(Integer.valueOf(value.toString()));
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        if (_attributes == null)
+            _attributes = new AttributesMap();
+        _attributes.setAttribute(name,value);
+
+        if (_requestAttributeListeners != null)
+        {
+            final ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(_context,this,name,old_value == null?value:old_value);
+            final int size = LazyList.size(_requestAttributeListeners);
+            for (int i = 0; i < size; i++)
+            {
+                final EventListener listener = (ServletRequestAttributeListener)LazyList.get(_requestAttributeListeners,i);
+                if (listener instanceof ServletRequestAttributeListener)
+                {
+                    final ServletRequestAttributeListener l = (ServletRequestAttributeListener)listener;
+
+                    if (old_value == null)
+                        l.attributeAdded(event);
+                    else if (value == null)
+                        l.attributeRemoved(event);
+                    else
+                        l.attributeReplaced(event);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes = attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the authentication.
+     *
+     * @param authentication
+     *            the authentication to set
+     */
+    public void setAuthentication(Authentication authentication)
+    {
+        _authentication = authentication;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException
+    {
+        if (_inputState != __NONE)
+            return;
+
+        _characterEncoding = encoding;
+
+        // check encoding is supported
+        if (!StringUtil.isUTF8(encoding))
+            // noinspection ResultOfMethodCallIgnored
+            "".getBytes(encoding);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+     */
+    public void setCharacterEncodingUnchecked(String encoding)
+    {
+        _characterEncoding = encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    // final so we can safely call this from constructor
+    protected final void setConnection(AbstractHttpConnection connection)
+    {
+        _connection = connection;
+        _async.setConnection(connection);
+        _endp = connection.getEndPoint();
+        _dns = connection.getResolveNames();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletRequest#getContentType()
+     */
+    public void setContentType(String contentType)
+    {
+        _connection.getRequestFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,contentType);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set request context
+     *
+     * @param context
+     *            context object
+     */
+    public void setContext(Context context)
+    {
+        _newContext = _context != context;
+        _context = context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this is the first call of {@link #takeNewContext()} since the last
+     *         {@link #setContext(org.eclipse.jetty.server.handler.ContextHandler.Context)} call.
+     */
+    public boolean takeNewContext()
+    {
+        boolean nc = _newContext;
+        _newContext = false;
+        return nc;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the "context path" for this request
+     *
+     * @see HttpServletRequest#getContextPath()
+     */
+    public void setContextPath(String contextPath)
+    {
+        _contextPath = contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cookies
+     *            The cookies to set.
+     */
+    public void setCookies(Cookie[] cookies)
+    {
+        if (_cookies == null)
+            _cookies = new CookieCutter();
+        _cookies.setCookies(cookies);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherType(DispatcherType type)
+    {
+        _dispatcherType = type;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setHandled(boolean h)
+    {
+        _handled = h;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param method
+     *            The method to set.
+     */
+    public void setMethod(String method)
+    {
+        _method = method;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param parameters
+     *            The parameters to set.
+     */
+    public void setParameters(MultiMap<String> parameters)
+    {
+        _parameters = (parameters == null)?_baseParameters:parameters;
+        if (_paramsExtracted && _parameters == null)
+            throw new IllegalStateException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathInfo
+     *            The pathInfo to set.
+     */
+    public void setPathInfo(String pathInfo)
+    {
+        _pathInfo = pathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocol
+     *            The protocol to set.
+     */
+    public void setProtocol(String protocol)
+    {
+        _protocol = protocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the character encoding used for the query string. This call will effect the return of getQueryString and getParamaters. It must be called before any
+     * geParameter methods.
+     *
+     * The request attribute "org.eclipse.jetty.server.server.Request.queryEncoding" may be set as an alternate method of calling setQueryEncoding.
+     *
+     * @param queryEncoding
+     */
+    public void setQueryEncoding(String queryEncoding)
+    {
+        _queryEncoding = queryEncoding;
+        _queryString = null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param queryString
+     *            The queryString to set.
+     */
+    public void setQueryString(String queryString)
+    {
+        _queryString = queryString;
+        _queryEncoding = null; //assume utf-8
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param addr
+     *            The address to set.
+     */
+    public void setRemoteAddr(String addr)
+    {
+        _remoteAddr = addr;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param host
+     *            The host to set.
+     */
+    public void setRemoteHost(String host)
+    {
+        _remoteHost = host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionId
+     *            The requestedSessionId to set.
+     */
+    public void setRequestedSessionId(String requestedSessionId)
+    {
+        _requestedSessionId = requestedSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestedSessionIdCookie
+     *            The requestedSessionIdCookie to set.
+     */
+    public void setRequestedSessionIdFromCookie(boolean requestedSessionIdCookie)
+    {
+        _requestedSessionIdFromCookie = requestedSessionIdCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param requestURI
+     *            The requestURI to set.
+     */
+    public void setRequestURI(String requestURI)
+    {
+        _requestURI = requestURI;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param scheme
+     *            The scheme to set.
+     */
+    public void setScheme(String scheme)
+    {
+        _scheme = scheme;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param host
+     *            The host to set.
+     */
+    public void setServerName(String host)
+    {
+        _serverName = host;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param port
+     *            The port to set.
+     */
+    public void setServerPort(int port)
+    {
+        _port = port;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletPath
+     *            The servletPath to set.
+     */
+    public void setServletPath(String servletPath)
+    {
+        _servletPath = servletPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session
+     *            The session to set.
+     */
+    public void setSession(HttpSession session)
+    {
+        _session = session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        _sessionManager = sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setTimeStamp(long ts)
+    {
+        _timeStamp = ts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param uri
+     *            The uri to set.
+     */
+    public void setUri(HttpURI uri)
+    {
+        _uri = uri;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setUserIdentityScope(UserIdentity.Scope scope)
+    {
+        _scope = scope;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set timetstamp of request dispatch
+     *
+     * @param value
+     *            timestamp
+     */
+    public void setDispatchTime(long value)
+    {
+        _dispatchTime = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    public AsyncContext startAsync() throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        _async.startAsync();
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
+    {
+        if (!_asyncSupported)
+            throw new IllegalStateException("!asyncSupported");
+        _async.startAsync(_context,servletRequest,servletResponse);
+        return _async;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return (_handled?"[":"(") + getMethod() + " " + _uri + (_handled?"]@":")@") + hashCode() + " " + super.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred)
+        {
+            setAuthentication(((Authentication.Deferred)_authentication).authenticate(this,response));
+            return !(_authentication instanceof Authentication.ResponseSent);        
+        }
+        response.sendError(HttpStatus.UNAUTHORIZED_401);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Part getPart(String name) throws IOException, ServletException
+    {                
+        getParts();
+        return _multiPartInputStream.getPart(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        if (getContentType() == null || !getContentType().startsWith("multipart/form-data"))
+            throw new ServletException("Content-Type != multipart/form-data");
+        
+        if (_multiPartInputStream == null)
+            _multiPartInputStream = (MultiPartInputStream)getAttribute(__MULTIPART_INPUT_STREAM);
+        
+        if (_multiPartInputStream == null)
+        {
+            MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
+            
+            if (config == null)
+                throw new IllegalStateException("No multipart config for servlet");
+            
+            _multiPartInputStream = new MultiPartInputStream(getInputStream(), 
+                                                             getContentType(), config, 
+                                                             (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
+            
+            setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
+            setAttribute(__MULTIPART_CONTEXT, _context);
+            Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing 
+            for (Part p:parts)
+            {
+                MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
+                if (mp.getContentDispositionFilename() == null)
+                {
+                    //Servlet Spec 3.0 pg 23, parts without filenames must be put into init params
+                    String charset = null;
+                    if (mp.getContentType() != null)
+                        charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType()));
+
+                    ByteArrayOutputStream os = null;
+                    InputStream is = mp.getInputStream(); //get the bytes regardless of being in memory or in temp file
+                    try
+                    {
+                        os = new ByteArrayOutputStream();
+                        IO.copy(is, os);
+                        String content=new String(os.toByteArray(),charset==null?StringUtil.__UTF8:charset);   
+                        getParameter(""); //cause params to be evaluated
+                        getParameters().add(mp.getName(), content);
+                    }
+                    finally
+                    {
+                        IO.close(os);
+                        IO.close(is);
+                    }
+                }
+            }
+        }
+
+        return _multiPartInputStream.getParts();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void login(String username, String password) throws ServletException
+    {
+        if (_authentication instanceof Authentication.Deferred) 
+        {
+            _authentication=((Authentication.Deferred)_authentication).login(username,password,this);
+            if (_authentication == null)
+                throw new ServletException();
+        } 
+        else 
+        {
+            throw new ServletException("Authenticated as "+_authentication);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void logout() throws ServletException
+    {
+        if (_authentication instanceof Authentication.User)
+            ((Authentication.User)_authentication).logout();
+        _authentication=Authentication.UNAUTHENTICATED;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Merge in a new query string. The query string is merged with the existing parameters and {@link #setParameters(MultiMap)} and
+     * {@link #setQueryString(String)} are called with the result. The merge is according to the rules of the servlet dispatch forward method.
+     *
+     * @param query
+     *            The query string to merge into the request.
+     */
+    public void mergeQueryString(String query)
+    {
+        // extract parameters from dispatch query
+        MultiMap<String> parameters = new MultiMap<String>();
+        UrlEncoded.decodeTo(query,parameters, StringUtil.__UTF8); //have to assume UTF-8 because we can't know otherwise
+
+        boolean merge_old_query = false;
+
+        // Have we evaluated parameters
+        if (!_paramsExtracted)
+            extractParameters();
+
+        // Are there any existing parameters?
+        if (_parameters != null && _parameters.size() > 0)
+        {
+            // Merge parameters; new parameters of the same name take precedence.
+            Iterator<Entry<String, Object>> iter = _parameters.entrySet().iterator();
+            while (iter.hasNext())
+            {
+                Map.Entry<String, Object> entry = iter.next();
+                String name = entry.getKey();
+
+                // If the names match, we will need to remake the query string
+                if (parameters.containsKey(name))
+                    merge_old_query = true;
+
+                // Add the old values to the new parameter map
+                Object values = entry.getValue();
+                for (int i = 0; i < LazyList.size(values); i++)
+                    parameters.add(name,LazyList.get(values,i));
+            }
+        }
+
+        if (_queryString != null && _queryString.length() > 0)
+        {
+            if (merge_old_query)
+            {
+                StringBuilder overridden_query_string = new StringBuilder();
+                MultiMap<String> overridden_old_query = new MultiMap<String>();
+                UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding());//decode using any queryencoding set for the request
+                
+                
+                MultiMap<String> overridden_new_query = new MultiMap<String>();
+                UrlEncoded.decodeTo(query,overridden_new_query,StringUtil.__UTF8); //have to assume utf8 as we cannot know otherwise
+
+                Iterator<Entry<String, Object>> iter = overridden_old_query.entrySet().iterator();
+                while (iter.hasNext())
+                {
+                    Map.Entry<String, Object> entry = iter.next();
+                    String name = entry.getKey();
+                    if (!overridden_new_query.containsKey(name))
+                    {
+                        Object values = entry.getValue();
+                        for (int i = 0; i < LazyList.size(values); i++)
+                        {
+                            overridden_query_string.append("&").append(name).append("=").append(LazyList.get(values,i));
+                        }
+                    }
+                }
+
+                query = query + overridden_query_string;
+            }
+            else
+            {
+                query = query + "&" + _queryString;
+            }
+        }
+
+        setParameters(parameters);
+        setQueryString(query);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/RequestLog.java b/src/java/org/eclipse/jetty/server/RequestLog.java
new file mode 100644
index 0000000..0cb1a53
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/RequestLog.java
@@ -0,0 +1,30 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server; 
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** 
+ * A <code>RequestLog</code> can be attached to a {@link org.eclipse.jetty.server.handler.RequestLogHandler} to enable 
+ * logging of requests/responses.
+ */
+public interface RequestLog extends LifeCycle
+{
+    public void log(Request request, Response response);
+}
diff --git a/src/java/org/eclipse/jetty/server/ResourceCache.java b/src/java/org/eclipse/jetty/server/ResourceCache.java
new file mode 100644
index 0000000..204d71e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ResourceCache.java
@@ -0,0 +1,522 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.View;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * 
+ */
+public class ResourceCache
+{
+    private static final Logger LOG = Log.getLogger(ResourceCache.class);
+
+    private final ConcurrentMap<String,Content> _cache;
+    private final AtomicInteger _cachedSize;
+    private final AtomicInteger _cachedFiles;
+    private final ResourceFactory _factory;
+    private final ResourceCache _parent;
+    private final MimeTypes _mimeTypes;
+    private final boolean _etags;
+
+    private boolean  _useFileMappedBuffer=true;
+    private int _maxCachedFileSize =4*1024*1024;
+    private int _maxCachedFiles=2048;
+    private int _maxCacheSize =32*1024*1024;
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * @param mimeTypes Mimetype to use for meta data
+     */
+    public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
+    {
+        _factory = factory;
+        _cache=new ConcurrentHashMap<String,Content>();
+        _cachedSize=new AtomicInteger();
+        _cachedFiles=new AtomicInteger();
+        _mimeTypes=mimeTypes;
+        _parent=parent;
+        _etags=etags;
+        _useFileMappedBuffer=useFileMappedBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getCachedSize()
+    {
+        return _cachedSize.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getCachedFiles()
+    {
+        return _cachedFiles.get();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getMaxCachedFileSize()
+    {
+        return _maxCachedFileSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCachedFileSize(int maxCachedFileSize)
+    {
+        _maxCachedFileSize = maxCachedFileSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxCacheSize()
+    {
+        return _maxCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMaxCacheSize(int maxCacheSize)
+    {
+        _maxCacheSize = maxCacheSize;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the maxCachedFiles.
+     */
+    public int getMaxCachedFiles()
+    {
+        return _maxCachedFiles;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCachedFiles The maxCachedFiles to set.
+     */
+    public void setMaxCachedFiles(int maxCachedFiles)
+    {
+        _maxCachedFiles = maxCachedFiles;
+        shrinkCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isUseFileMappedBuffer()
+    {
+        return _useFileMappedBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
+    {
+        _useFileMappedBuffer = useFileMappedBuffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void flushCache()
+    {
+        if (_cache!=null)
+        {
+            while (_cache.size()>0)
+            {
+                for (String path : _cache.keySet())
+                {
+                    Content content = _cache.remove(path);
+                    if (content!=null)
+                        content.invalidate();
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get a Entry from the cache.
+     * Get either a valid entry object or create a new one if possible.
+     *
+     * @param pathInContext The key into the cache
+     * @return The entry matching <code>pathInContext</code>, or a new entry 
+     * if no matching entry was found. If the content exists but is not cachable, 
+     * then a {@link ResourceAsHttpContent} instance is return. If 
+     * the resource does not exist, then null is returned.
+     * @throws IOException Problem loading the resource
+     */
+    public HttpContent lookup(String pathInContext)
+        throws IOException
+    {
+        // Is the content in this cache?
+        Content content =_cache.get(pathInContext);
+        if (content!=null && (content).isValid())
+            return content;
+       
+        // try loading the content from our factory.
+        Resource resource=_factory.getResource(pathInContext);
+        HttpContent loaded = load(pathInContext,resource);
+        if (loaded!=null)
+            return loaded;
+        
+        // Is the content in the parent cache?
+        if (_parent!=null)
+        {
+            HttpContent httpContent=_parent.lookup(pathInContext);
+            if (httpContent!=null)
+                return httpContent;
+        }
+        
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resource
+     * @return True if the resource is cacheable. The default implementation tests the cache sizes.
+     */
+    protected boolean isCacheable(Resource resource)
+    {
+        long len = resource.length();
+
+        // Will it fit in the cache?
+        return  (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
+    }
+    
+    /* ------------------------------------------------------------ */
+    private HttpContent load(String pathInContext, Resource resource)
+        throws IOException
+    {
+        Content content=null;
+        
+        if (resource==null || !resource.exists())
+            return null;
+        
+        // Will it fit in the cache?
+        if (!resource.isDirectory() && isCacheable(resource))
+        {   
+            // Create the Content (to increment the cache sizes before adding the content 
+            content = new Content(pathInContext,resource);
+
+            // reduce the cache to an acceptable size.
+            shrinkCache();
+
+            // Add it to the cache.
+            Content added = _cache.putIfAbsent(pathInContext,content);
+            if (added!=null)
+            {
+                content.invalidate();
+                content=added;
+            }
+
+            return content;
+        }
+        
+        return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags);
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void shrinkCache()
+    {
+        // While we need to shrink
+        while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
+        {
+            // Scan the entire cache and generate an ordered list by last accessed time.
+            SortedSet<Content> sorted= new TreeSet<Content>(
+                    new Comparator<Content>()
+                    {
+                        public int compare(Content c1, Content c2)
+                        {
+                            if (c1._lastAccessed<c2._lastAccessed)
+                                return -1;
+                            
+                            if (c1._lastAccessed>c2._lastAccessed)
+                                return 1;
+
+                            if (c1._length<c2._length)
+                                return -1;
+                            
+                            return c1._key.compareTo(c2._key);
+                        }
+                    });
+            for (Content content : _cache.values())
+                sorted.add(content);
+            
+            // Invalidate least recently used first
+            for (Content content : sorted)
+            {
+                if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
+                    break;
+                if (content==_cache.remove(content.getKey()))
+                    content.invalidate();
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected Buffer getIndirectBuffer(Resource resource)
+    {
+        try
+        {
+            int len=(int)resource.length();
+            if (len<0)
+            {
+                LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
+                return null;
+            }
+            Buffer buffer = new IndirectNIOBuffer(len);
+            InputStream is = resource.getInputStream();
+            buffer.readFrom(is,len);
+            is.close();
+            return buffer;
+        }
+        catch(IOException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Buffer getDirectBuffer(Resource resource)
+    {
+        try
+        {
+            if (_useFileMappedBuffer && resource.getFile()!=null) 
+                return new DirectNIOBuffer(resource.getFile());
+
+            int len=(int)resource.length();
+            if (len<0)
+            {
+                LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
+                return null;
+            }
+            Buffer buffer = new DirectNIOBuffer(len);
+            InputStream is = resource.getInputStream();
+            buffer.readFrom(is,len);
+            is.close();
+            return buffer;
+        }
+        catch(IOException e)
+        {
+            LOG.warn(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** MetaData associated with a context Resource.
+     */
+    public class Content implements HttpContent
+    {
+        final Resource _resource;
+        final int _length;
+        final String _key;
+        final long _lastModified;
+        final Buffer _lastModifiedBytes;
+        final Buffer _contentType;
+        final Buffer _etagBuffer;
+        
+        volatile long _lastAccessed;
+        AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
+        AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
+
+        /* ------------------------------------------------------------ */
+        Content(String pathInContext,Resource resource)
+        {
+            _key=pathInContext;
+            _resource=resource;
+
+            _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
+            boolean exists=resource.exists();
+            _lastModified=exists?resource.lastModified():-1;
+            _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
+            
+            _length=exists?(int)resource.length():0;
+            _cachedSize.addAndGet(_length);
+            _cachedFiles.incrementAndGet();
+            _lastAccessed=System.currentTimeMillis();
+            
+            _etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null;
+        }
+
+
+        /* ------------------------------------------------------------ */
+        public String getKey()
+        {
+            return _key;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isCached()
+        {
+            return _key!=null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean isMiss()
+        {
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Resource getResource()
+        {
+            return _resource;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getETag()
+        {
+            return _etagBuffer;
+        }
+        
+        /* ------------------------------------------------------------ */
+        boolean isValid()
+        {
+            if (_lastModified==_resource.lastModified() && _length==_resource.length())
+            {
+                _lastAccessed=System.currentTimeMillis();
+                return true;
+            }
+
+            if (this==_cache.remove(_key))
+                invalidate();
+            return false;
+        }
+
+        /* ------------------------------------------------------------ */
+        protected void invalidate()
+        {
+            // Invalidate it
+            _cachedSize.addAndGet(-_length);
+            _cachedFiles.decrementAndGet();
+            _resource.release(); 
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getLastModified()
+        {
+            return _lastModifiedBytes;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getContentType()
+        {
+            return _contentType;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void release()
+        {
+            // don't release while cached. Release when invalidated.
+        }
+
+        /* ------------------------------------------------------------ */
+        public Buffer getIndirectBuffer()
+        {
+            Buffer buffer = _indirectBuffer.get();
+            if (buffer==null)
+            {
+                Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
+                
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_indirectBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_indirectBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+            return new View(buffer);
+        }
+        
+
+        /* ------------------------------------------------------------ */
+        public Buffer getDirectBuffer()
+        {
+            Buffer buffer = _directBuffer.get();
+            if (buffer==null)
+            {
+                Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
+
+                if (buffer2==null)
+                    LOG.warn("Could not load "+this);
+                else if (_directBuffer.compareAndSet(null,buffer2))
+                    buffer=buffer2;
+                else
+                    buffer=_directBuffer.get();
+            }
+            if (buffer==null)
+                return null;
+                        
+            return new View(buffer);
+        }
+        
+        /* ------------------------------------------------------------ */
+        public long getContentLength()
+        {
+            return _length;
+        }
+
+        /* ------------------------------------------------------------ */
+        public InputStream getInputStream() throws IOException
+        {
+            Buffer indirect = getIndirectBuffer();
+            if (indirect!=null && indirect.array()!=null)
+                return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
+           
+            return _resource.getInputStream();
+        }   
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
+        }   
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/Response.java b/src/java/org/eclipse/jetty/server/Response.java
new file mode 100644
index 0000000..d9d3a98
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Response.java
@@ -0,0 +1,1307 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Enumeration;
+import java.util.Locale;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.HttpVersions;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.BufferCache.CachedBuffer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** Response.
+ * <p>
+ * Implements {@link javax.servlet.http.HttpServletResponse} from the <code>javax.servlet.http</code> package.
+ * </p>
+ */
+public class Response implements HttpServletResponse
+{
+    private static final Logger LOG = Log.getLogger(Response.class);
+
+    
+    public static final int
+        NONE=0,
+        STREAM=1,
+        WRITER=2;
+
+    /**
+     * If a header name starts with this string,  the header (stripped of the prefix)
+     * can be set during include using only {@link #setHeader(String, String)} or
+     * {@link #addHeader(String, String)}.
+     */
+    public final static String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
+
+    /**
+     * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie 
+     * will be set as HTTP ONLY.
+     */
+    public final static String HTTP_ONLY_COMMENT="__HTTP_ONLY__";
+    
+    
+    /* ------------------------------------------------------------ */
+    public static Response getResponse(HttpServletResponse response)
+    {
+        if (response instanceof Response)
+            return (Response)response;
+
+        return AbstractHttpConnection.getCurrentConnection().getResponse();
+    }
+    
+    private final AbstractHttpConnection _connection;
+    private int _status=SC_OK;
+    private String _reason;
+    private Locale _locale;
+    private String _mimeType;
+    private CachedBuffer _cachedMimeType;
+    private String _characterEncoding;
+    private boolean _explicitEncoding;
+    private String _contentType;
+    private volatile int _outputState;
+    private PrintWriter _writer;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public Response(AbstractHttpConnection connection)
+    {
+        _connection=connection;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#reset()
+     */
+    protected void recycle()
+    {
+        _status=SC_OK;
+        _reason=null;
+        _locale=null;
+        _mimeType=null;
+        _cachedMimeType=null;
+        _characterEncoding=null;
+        _explicitEncoding=false;
+        _contentType=null;
+        _writer=null;
+        _outputState=NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
+     */
+    public void addCookie(HttpCookie cookie)
+    {
+        _connection.getResponseFields().addSetCookie(cookie);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#addCookie(javax.servlet.http.Cookie)
+     */
+    public void addCookie(Cookie cookie)
+    {
+        String comment=cookie.getComment();
+        boolean http_only=false;
+        
+        if (comment!=null)
+        {
+            int i=comment.indexOf(HTTP_ONLY_COMMENT);
+            if (i>=0)
+            {
+                http_only=true;
+                comment=comment.replace(HTTP_ONLY_COMMENT,"").trim();
+                if (comment.length()==0)
+                    comment=null;
+            }
+        }
+        _connection.getResponseFields().addSetCookie(cookie.getName(),
+                cookie.getValue(),
+                cookie.getDomain(),
+                cookie.getPath(),
+                cookie.getMaxAge(),
+                comment,
+                cookie.getSecure(),
+                http_only || cookie.isHttpOnly(),
+                cookie.getVersion());
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#containsHeader(java.lang.String)
+     */
+    public boolean containsHeader(String name)
+    {
+        return _connection.getResponseFields().containsKey(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#encodeURL(java.lang.String)
+     */
+    public String encodeURL(String url)
+    {
+        final Request request=_connection.getRequest();
+        SessionManager sessionManager = request.getSessionManager();
+        if (sessionManager==null)
+            return url;
+        
+        HttpURI uri = null;
+        if (sessionManager.isCheckingRemoteSessionIdEncoding() && URIUtil.hasScheme(url))
+        {
+            uri = new HttpURI(url);
+            String path = uri.getPath();
+            path = (path == null?"":path);
+            int port=uri.getPort();
+            if (port<0) 
+                port = HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme())?443:80;
+            if (!request.getServerName().equalsIgnoreCase(uri.getHost()) ||
+                request.getServerPort()!=port ||
+                !path.startsWith(request.getContextPath())) //TODO the root context path is "", with which every non null string starts
+                return url;
+        }
+        
+        String sessionURLPrefix = sessionManager.getSessionIdPathParameterNamePrefix();
+        if (sessionURLPrefix==null)
+            return url;
+
+        if (url==null)
+            return null;
+        
+        // should not encode if cookies in evidence
+        if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) 
+        {
+            int prefix=url.indexOf(sessionURLPrefix);
+            if (prefix!=-1)
+            {
+                int suffix=url.indexOf("?",prefix);
+                if (suffix<0)
+                    suffix=url.indexOf("#",prefix);
+
+                if (suffix<=prefix)
+                    return url.substring(0,prefix);
+                return url.substring(0,prefix)+url.substring(suffix);
+            }
+            return url;
+        }
+
+        // get session;
+        HttpSession session=request.getSession(false);
+
+        // no session
+        if (session == null)
+            return url;
+
+        // invalid session
+        if (!sessionManager.isValid(session))
+            return url;
+
+        String id=sessionManager.getNodeId(session);
+
+        if (uri == null)
+                uri = new HttpURI(url);
+     
+        
+        // Already encoded
+        int prefix=url.indexOf(sessionURLPrefix);
+        if (prefix!=-1)
+        {
+            int suffix=url.indexOf("?",prefix);
+            if (suffix<0)
+                suffix=url.indexOf("#",prefix);
+
+            if (suffix<=prefix)
+                return url.substring(0,prefix+sessionURLPrefix.length())+id;
+            return url.substring(0,prefix+sessionURLPrefix.length())+id+
+                url.substring(suffix);
+        }
+
+        // edit the session
+        int suffix=url.indexOf('?');
+        if (suffix<0)
+            suffix=url.indexOf('#');
+        if (suffix<0) 
+        {          
+            return url+ 
+                   ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"") + //if no path, insert the root path
+                   sessionURLPrefix+id;
+        }
+     
+        
+        return url.substring(0,suffix)+
+            ((HttpSchemes.HTTPS.equalsIgnoreCase(uri.getScheme()) || HttpSchemes.HTTP.equalsIgnoreCase(uri.getScheme())) && uri.getPath()==null?"/":"")+ //if no path so insert the root path
+            sessionURLPrefix+id+url.substring(suffix);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.http.HttpServletResponse#encodeRedirectURL(java.lang.String)
+     */
+    public String encodeRedirectURL(String url)
+    {
+        return encodeURL(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String encodeUrl(String url)
+    {
+        return encodeURL(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String encodeRedirectUrl(String url)
+    {
+        return encodeRedirectURL(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#sendError(int, java.lang.String)
+     */
+    public void sendError(int code, String message) throws IOException
+    {
+    	if (_connection.isIncluding())
+    		return;
+
+        if (isCommitted())
+            LOG.warn("Committed before "+code+" "+message);
+
+        resetBuffer();
+        _characterEncoding=null;
+        setHeader(HttpHeaders.EXPIRES,null);
+        setHeader(HttpHeaders.LAST_MODIFIED,null);
+        setHeader(HttpHeaders.CACHE_CONTROL,null);
+        setHeader(HttpHeaders.CONTENT_TYPE,null);
+        setHeader(HttpHeaders.CONTENT_LENGTH,null);
+
+        _outputState=NONE;
+        setStatus(code,message);
+
+        if (message==null)
+            message=HttpStatus.getMessage(code);
+
+        // If we are allowed to have a body
+        if (code!=SC_NO_CONTENT &&
+            code!=SC_NOT_MODIFIED &&
+            code!=SC_PARTIAL_CONTENT &&
+            code>=SC_OK)
+        {
+            Request request = _connection.getRequest();
+
+            ErrorHandler error_handler = null;
+            ContextHandler.Context context = request.getContext();
+            if (context!=null)
+                error_handler=context.getContextHandler().getErrorHandler();
+            if (error_handler==null)
+                error_handler = _connection.getConnector().getServer().getBean(ErrorHandler.class);
+            if (error_handler!=null)
+            {
+                request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code));
+                request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
+                request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
+                request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName());
+                error_handler.handle(null,_connection.getRequest(),_connection.getRequest(),this );
+            }
+            else
+            {
+                setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
+                setContentType(MimeTypes.TEXT_HTML_8859_1);
+                ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);
+                if (message != null)
+                {
+                    message= StringUtil.replace(message, "&", "&amp;");
+                    message= StringUtil.replace(message, "<", "&lt;");
+                    message= StringUtil.replace(message, ">", "&gt;");
+                }
+                String uri= request.getRequestURI();
+                if (uri!=null)
+                {
+                    uri= StringUtil.replace(uri, "&", "&amp;");
+                    uri= StringUtil.replace(uri, "<", "&lt;");
+                    uri= StringUtil.replace(uri, ">", "&gt;");
+                }
+
+                writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n");
+                writer.write("<title>Error ");
+                writer.write(Integer.toString(code));
+                writer.write(' ');
+                if (message==null)
+                    message=HttpStatus.getMessage(code);
+                writer.write(message);
+                writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: ");
+                writer.write(Integer.toString(code));
+                writer.write("</h2>\n<p>Problem accessing ");
+                writer.write(uri);
+                writer.write(". Reason:\n<pre>    ");
+                writer.write(message);
+                writer.write("</pre>");
+                writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>");
+
+                for (int i= 0; i < 20; i++)
+                    writer.write("\n                                                ");
+                writer.write("\n</body>\n</html>\n");
+
+                writer.flush();
+                setContentLength(writer.size());
+                writer.writeTo(getOutputStream());
+                writer.destroy();
+            }
+        }
+        else if (code!=SC_PARTIAL_CONTENT)
+        {
+            _connection.getRequestFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
+            _connection.getRequestFields().remove(HttpHeaders.CONTENT_LENGTH_BUFFER);
+            _characterEncoding=null;
+            _mimeType=null;
+            _cachedMimeType=null;
+        }
+
+        complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#sendError(int)
+     */
+    public void sendError(int sc) throws IOException
+    {
+        if (sc==102)
+            sendProcessing();
+        else
+            sendError(sc,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Send a 102-Processing response.
+     * If the connection is a HTTP connection, the version is 1.1 and the
+     * request has a Expect header starting with 102, then a 102 response is
+     * sent. This indicates that the request still be processed and real response
+     * can still be sent.   This method is called by sendError if it is passed 102.
+     * @see javax.servlet.http.HttpServletResponse#sendError(int)
+     */
+    public void sendProcessing() throws IOException
+    {
+        if (_connection.isExpecting102Processing() && !isCommitted())
+            ((HttpGenerator)_connection.getGenerator()).send1xx(HttpStatus.PROCESSING_102);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
+     */
+    public void sendRedirect(String location) throws IOException
+    {
+    	if (_connection.isIncluding())
+    		return;
+
+        if (location==null)
+            throw new IllegalArgumentException();
+
+        if (!URIUtil.hasScheme(location))
+        {
+            StringBuilder buf = _connection.getRequest().getRootURL();
+            if (location.startsWith("/"))
+                buf.append(location);
+            else
+            {
+                String path=_connection.getRequest().getRequestURI();
+                String parent=(path.endsWith("/"))?path:URIUtil.parentPath(path);
+                location=URIUtil.addPaths(parent,location);
+                if(location==null)
+                    throw new IllegalStateException("path cannot be above root");
+                if (!location.startsWith("/"))
+                    buf.append('/');
+                buf.append(location);
+            }
+
+            location=buf.toString();
+            HttpURI uri = new HttpURI(location);
+            String path=uri.getDecodedPath();
+            String canonical=URIUtil.canonicalPath(path);
+            if (canonical==null)
+                throw new IllegalArgumentException();
+            if (!canonical.equals(path))
+            {
+                buf = _connection.getRequest().getRootURL();
+                buf.append(URIUtil.encodePath(canonical));
+                String param=uri.getParam();
+                if (param!=null)
+                {
+                    buf.append(';');
+                    buf.append(param);
+                }
+                String query=uri.getQuery();
+                if (query!=null)
+                {
+                    buf.append('?');
+                    buf.append(query);
+                }
+                String fragment=uri.getFragment();
+                if (fragment!=null)
+                {
+                    buf.append('#');
+                    buf.append(fragment);
+                }
+                location=buf.toString();
+            }
+        }
+        
+        resetBuffer();
+        setHeader(HttpHeaders.LOCATION,location);
+        setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
+        complete();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
+     */
+    public void setDateHeader(String name, long date)
+    {
+        if (!_connection.isIncluding())
+            _connection.getResponseFields().putDateField(name, date);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
+     */
+    public void addDateHeader(String name, long date)
+    {
+        if (!_connection.isIncluding())
+            _connection.getResponseFields().addDateField(name, date);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
+     */
+    public void setHeader(String name, String value)
+    {
+        if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
+            setContentType(value);
+        else
+        {
+            if (_connection.isIncluding())
+            {
+                if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                    name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+                else
+                    return;
+            }
+            _connection.getResponseFields().put(name, value);
+            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+            {
+                if (value==null)
+                    _connection._generator.setContentLength(-1);
+                else
+                    _connection._generator.setContentLength(Long.parseLong(value));
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Collection<String> getHeaderNames()
+    {
+        final HttpFields fields=_connection.getResponseFields();
+        return fields.getFieldNamesCollection();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String getHeader(String name)
+    {
+        return _connection.getResponseFields().getStringField(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Collection<String> getHeaders(String name)
+    {
+        final HttpFields fields=_connection.getResponseFields();
+        Collection<String> i = fields.getValuesCollection(name);
+        if (i==null)
+            return Collections.EMPTY_LIST;
+        return i;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
+     */
+    public void addHeader(String name, String value)
+    {
+
+        if (_connection.isIncluding())
+        {
+            if (name.startsWith(SET_INCLUDE_HEADER_PREFIX))
+                name=name.substring(SET_INCLUDE_HEADER_PREFIX.length());
+            else
+                return;
+        }
+
+        if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(name))
+        {
+            setContentType(value);
+            return;
+        }
+        
+        _connection.getResponseFields().add(name, value);
+        if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+            _connection._generator.setContentLength(Long.parseLong(value));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
+     */
+    public void setIntHeader(String name, int value)
+    {
+        if (!_connection.isIncluding())
+        {
+            _connection.getResponseFields().putLongField(name, value);
+            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+                _connection._generator.setContentLength(value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
+     */
+    public void addIntHeader(String name, int value)
+    {
+        if (!_connection.isIncluding())
+        {
+            _connection.getResponseFields().addLongField(name, value);
+            if (HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(name))
+                _connection._generator.setContentLength(value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#setStatus(int)
+     */
+    public void setStatus(int sc)
+    {
+        setStatus(sc,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpServletResponse#setStatus(int, java.lang.String)
+     */
+    public void setStatus(int sc, String sm)
+    {
+        if (sc<=0)
+            throw new IllegalArgumentException();
+        if (!_connection.isIncluding())
+        {
+            _status=sc;
+            _reason=sm;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getCharacterEncoding()
+     */
+    public String getCharacterEncoding()
+    {
+        if (_characterEncoding==null)
+            _characterEncoding=StringUtil.__ISO_8859_1;
+        return _characterEncoding;
+    }
+    
+    /* ------------------------------------------------------------ */
+    String getSetCharacterEncoding()
+    {
+        return _characterEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getContentType()
+     */
+    public String getContentType()
+    {
+        return _contentType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getOutputStream()
+     */
+    public ServletOutputStream getOutputStream() throws IOException
+    {
+        if (_outputState!=NONE && _outputState!=STREAM)
+            throw new IllegalStateException("WRITER");
+
+        ServletOutputStream out = _connection.getOutputStream();
+        _outputState=STREAM;
+        return out;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isWriting()
+    {
+        return _outputState==WRITER;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isOutputing()
+    {
+        return _outputState!=NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getWriter()
+     */
+    public PrintWriter getWriter() throws IOException
+    {
+        if (_outputState!=NONE && _outputState!=WRITER)
+            throw new IllegalStateException("STREAM");
+
+        /* if there is no writer yet */
+        if (_writer==null)
+        {
+            /* get encoding from Content-Type header */
+            String encoding = _characterEncoding;
+
+            if (encoding==null)
+            {
+                /* implementation of educated defaults */
+                if(_cachedMimeType != null)
+                    encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
+
+                if (encoding==null)
+                    encoding = StringUtil.__ISO_8859_1;
+
+                setCharacterEncoding(encoding);
+            }
+
+            /* construct Writer using correct encoding */
+            _writer = _connection.getPrintWriter(encoding);
+        }
+        _outputState=WRITER;
+        return _writer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setCharacterEncoding(java.lang.String)
+     */
+    public void setCharacterEncoding(String encoding)
+    {
+    	if (_connection.isIncluding())
+    		return;
+
+        if (this._outputState==0 && !isCommitted())
+        {
+            _explicitEncoding=true;
+
+            if (encoding==null)
+            {
+                // Clear any encoding.
+                if (_characterEncoding!=null)
+                {
+                    _characterEncoding=null;
+                    if (_cachedMimeType!=null)
+                        _contentType=_cachedMimeType.toString();
+                    else if (_mimeType!=null)
+                        _contentType=_mimeType;
+                    else
+                        _contentType=null;
+
+                    if (_contentType==null)
+                        _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
+                    else
+                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                }
+            }
+            else
+            {
+                // No, so just add this one to the mimetype
+                _characterEncoding=encoding;
+                if (_contentType!=null)
+                {
+                    int i0=_contentType.indexOf(';');
+                    if (i0<0)
+                    {
+                        _contentType=null;
+                        if(_cachedMimeType!=null)
+                        {
+                            CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+                            if (content_type!=null)
+                            {
+                                _contentType=content_type.toString();
+                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+                            }
+                        }
+
+                        if (_contentType==null)
+                        {
+                            _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        }
+                    }
+                    else
+                    {
+                        int i1=_contentType.indexOf("charset=",i0);
+                        if (i1<0)
+                        {
+                            _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                        }
+                        else
+                        {
+                            int i8=i1+8;
+                            int i2=_contentType.indexOf(" ",i8);
+                            if (i2<0)
+                                _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                            else
+                                _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2);
+                        }
+                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setContentLength(int)
+     */
+    public void setContentLength(int len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || _connection.isIncluding())
+            return;
+        _connection._generator.setContentLength(len);
+        if (len>0)
+        {
+            _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
+            if (_connection._generator.isAllContentWritten())
+            {
+                if (_outputState==WRITER)
+                    _writer.close();
+                else if (_outputState==STREAM)
+                {
+                    try
+                    {
+                        getOutputStream().close();
+                    }
+                    catch(IOException e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setContentLength(int)
+     */
+    public void setLongContentLength(long len)
+    {
+        // Protect from setting after committed as default handling
+        // of a servlet HEAD request ALWAYS sets _content length, even
+        // if the getHandling committed the response!
+        if (isCommitted() || _connection.isIncluding())
+        	return;
+        _connection._generator.setContentLength(len);
+        _connection.getResponseFields().putLongField(HttpHeaders.CONTENT_LENGTH, len);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
+     */
+    public void setContentType(String contentType)
+    {
+        if (isCommitted() || _connection.isIncluding())
+            return;
+
+        // Yes this method is horribly complex.... but there are lots of special cases and
+        // as this method is called on every request, it is worth trying to save string creation.
+        //
+
+        if (contentType==null)
+        {
+            if (_locale==null)
+                _characterEncoding=null;
+            _mimeType=null;
+            _cachedMimeType=null;
+            _contentType=null;
+            _connection.getResponseFields().remove(HttpHeaders.CONTENT_TYPE_BUFFER);
+        }
+        else
+        {
+            // Look for encoding in contentType
+            int i0=contentType.indexOf(';');
+
+            if (i0>0)
+            {
+                // we have content type parameters
+
+                // Extract params off mimetype
+                _mimeType=contentType.substring(0,i0).trim();
+                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+
+                // Look for charset
+                int i1=contentType.indexOf("charset=",i0+1);
+                if (i1>=0)
+                {
+                    _explicitEncoding=true;
+                    int i8=i1+8;
+                    int i2 = contentType.indexOf(' ',i8);
+
+                    if (_outputState==WRITER)
+                    {
+                        // strip the charset and ignore;
+                        if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
+                        {
+                            if (_cachedMimeType!=null)
+                            {
+                                CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+                                if (content_type!=null)
+                                {
+                                    _contentType=content_type.toString();
+                                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+                                }
+                                else
+                                {
+                                    _contentType=_mimeType+";charset="+_characterEncoding;
+                                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                                }
+                            }
+                            else
+                            {
+                                _contentType=_mimeType+";charset="+_characterEncoding;
+                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                            }
+                        }
+                        else if (i2<0)
+                        {
+                            _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        }
+                        else
+                        {
+                            _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        }
+                    }
+                    else if ((i1==i0+1 && i2<0) || (i1==i0+2 && i2<0 && contentType.charAt(i0+1)==' '))
+                    {
+                        // The params are just the char encoding
+                        _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
+
+                        if (_cachedMimeType!=null)
+                        {
+                            CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+                            if (content_type!=null)
+                            {
+                                _contentType=content_type.toString();
+                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+                            }
+                            else
+                            {
+                                _contentType=contentType;
+                                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                            }
+                        }
+                        else
+                        {
+                            _contentType=contentType;
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        }
+                    }
+                    else if (i2>0)
+                    {
+                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8,i2));
+                        _contentType=contentType;
+                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    }
+                    else
+                    {
+                        _characterEncoding = QuotedStringTokenizer.unquote(contentType.substring(i8));
+                        _contentType=contentType;
+                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    }
+                }
+                else // No encoding in the params.
+                {
+                    _cachedMimeType=null;
+                    _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                }
+            }
+            else // No params at all
+            {
+                _mimeType=contentType;
+                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+
+                if (_characterEncoding!=null)
+                {
+                    if (_cachedMimeType!=null)
+                    {
+                        CachedBuffer content_type = _cachedMimeType.getAssociate(_characterEncoding);
+                        if (content_type!=null)
+                        {
+                            _contentType=content_type.toString();
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,content_type);
+                        }
+                        else
+                        {
+                            _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                            _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                        }
+                    }
+                    else
+                    {
+                        _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ");
+                        _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                    }
+                }
+                else if (_cachedMimeType!=null)
+                {
+                    _contentType=_cachedMimeType.toString();
+                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_cachedMimeType);
+                }
+                else
+                {
+                    _contentType=contentType;
+                    _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setBufferSize(int)
+     */
+    public void setBufferSize(int size)
+    {
+        if (isCommitted() || getContentCount()>0)
+            throw new IllegalStateException("Committed or content written");
+        _connection.getGenerator().increaseContentBufferSize(size);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getBufferSize()
+     */
+    public int getBufferSize()
+    {
+        return _connection.getGenerator().getContentBufferSize();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#flushBuffer()
+     */
+    public void flushBuffer() throws IOException
+    {
+        _connection.flushResponse();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#reset()
+     */
+    public void reset()
+    {
+        resetBuffer();
+        fwdReset();
+        _status=200;
+        _reason=null;
+        
+        HttpFields response_fields=_connection.getResponseFields();
+        
+        response_fields.clear();
+        String connection=_connection.getRequestFields().getStringField(HttpHeaders.CONNECTION_BUFFER);
+        if (connection!=null)
+        {
+            String[] values = connection.split(",");
+            for  (int i=0;values!=null && i<values.length;i++)
+            {
+                CachedBuffer cb = HttpHeaderValues.CACHE.get(values[0].trim());
+
+                if (cb!=null)
+                {
+                    switch(cb.getOrdinal())
+                    {
+                        case HttpHeaderValues.CLOSE_ORDINAL:
+                            response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.CLOSE_BUFFER);
+                            break;
+
+                        case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
+                            if (HttpVersions.HTTP_1_0.equalsIgnoreCase(_connection.getRequest().getProtocol()))
+                                response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.KEEP_ALIVE);
+                            break;
+                        case HttpHeaderValues.TE_ORDINAL:
+                            response_fields.put(HttpHeaders.CONNECTION_BUFFER,HttpHeaderValues.TE);
+                            break;
+                    }
+                }
+            }
+        }
+    }
+    
+
+    public void reset(boolean preserveCookies)
+    { 
+        if (!preserveCookies)
+            reset();
+        else
+        {
+            HttpFields response_fields=_connection.getResponseFields();
+
+            ArrayList<String> cookieValues = new ArrayList<String>(5);
+            Enumeration<String> vals = response_fields.getValues(HttpHeaders.SET_COOKIE);
+            while (vals.hasMoreElements())
+                cookieValues.add((String)vals.nextElement());
+
+            reset();
+
+            for (String v:cookieValues)
+                response_fields.add(HttpHeaders.SET_COOKIE, v);
+        }
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#reset()
+     */
+    public void fwdReset()
+    {
+        resetBuffer();
+
+        _writer=null;
+        _outputState=NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#resetBuffer()
+     */
+    public void resetBuffer()
+    {
+        if (isCommitted())
+            throw new IllegalStateException("Committed");
+        _connection.getGenerator().resetBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#isCommitted()
+     */
+    public boolean isCommitted()
+    {
+        return _connection.isResponseCommitted();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#setLocale(java.util.Locale)
+     */
+    public void setLocale(Locale locale)
+    {
+        if (locale == null || isCommitted() ||_connection.isIncluding())
+            return;
+
+        _locale = locale;
+        _connection.getResponseFields().put(HttpHeaders.CONTENT_LANGUAGE_BUFFER,locale.toString().replace('_','-'));
+
+        if (_explicitEncoding || _outputState!=0 )
+            return;
+
+        if (_connection.getRequest().getContext()==null)
+            return;
+
+        String charset = _connection.getRequest().getContext().getContextHandler().getLocaleEncoding(locale);
+
+        if (charset!=null && charset.length()>0)
+        {
+            _characterEncoding=charset;
+
+            /* get current MIME type from Content-Type header */
+            String type=getContentType();
+            if (type!=null)
+            {
+                _characterEncoding=charset;
+                int semi=type.indexOf(';');
+                if (semi<0)
+                {
+                    _mimeType=type;
+                    _contentType= type += ";charset="+charset;
+                }
+                else
+                {
+                    _mimeType=type.substring(0,semi);
+                    _contentType= _mimeType += ";charset="+charset;
+                }
+
+                _cachedMimeType=MimeTypes.CACHE.get(_mimeType);
+                _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletResponse#getLocale()
+     */
+    public Locale getLocale()
+    {
+        if (_locale==null)
+            return Locale.getDefault();
+        return _locale;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The HTTP status code that has been set for this request. This will be <code>200<code>
+     *    ({@link HttpServletResponse#SC_OK}), unless explicitly set through one of the <code>setStatus</code> methods.
+     */
+    public int getStatus()
+    {
+        return _status;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The reason associated with the current {@link #getStatus() status}. This will be <code>null</code>,
+     *    unless one of the <code>setStatus</code> methods have been called.
+     */
+    public String getReason()
+    {
+        return _reason;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void complete()
+        throws IOException
+    {
+        _connection.completeResponse();
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @return the number of bytes actually written in response body
+     */
+    public long getContentCount()
+    {
+        if (_connection==null || _connection.getGenerator()==null)
+            return -1;
+        return _connection.getGenerator().getContentWritten();
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpFields getHttpFields()
+    {
+        return _connection.getResponseFields();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "HTTP/1.1 "+_status+" "+ (_reason==null?"":_reason) +System.getProperty("line.separator")+
+        _connection.getResponseFields().toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullOutput extends ServletOutputStream
+    {
+        @Override
+        public void write(int b) throws IOException
+        {
+        }
+
+        @Override
+        public void print(String s) throws IOException
+        {
+        }
+
+        @Override
+        public void println(String s) throws IOException
+        {
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException
+        {
+        }
+
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/Server.java b/src/java/org/eclipse/jetty/server/Server.java
new file mode 100644
index 0000000..745f232
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/Server.java
@@ -0,0 +1,662 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Enumeration;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.component.Container;
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/* ------------------------------------------------------------ */
+/** Jetty HTTP Servlet Server.
+ * This class is the main class for the Jetty HTTP Servlet server.
+ * It aggregates Connectors (HTTP request receivers) and request Handlers.
+ * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
+ * to run jobs that will eventually call the handle method.
+ *
+ *  @org.apache.xbean.XBean  description="Creates an embedded Jetty web server"
+ */
+public class Server extends HandlerWrapper implements Attributes
+{
+    private static final Logger LOG = Log.getLogger(Server.class);
+
+    private static final String __version;
+    static
+    {
+        if (Server.class.getPackage()!=null &&
+            "Eclipse.org - Jetty".equals(Server.class.getPackage().getImplementationVendor()) &&
+             Server.class.getPackage().getImplementationVersion()!=null)
+            __version=Server.class.getPackage().getImplementationVersion();
+        else
+            __version=System.getProperty("jetty.version","8.y.z-SNAPSHOT");
+    }
+
+    private final Container _container=new Container();
+    private final AttributesMap _attributes = new AttributesMap();
+    private ThreadPool _threadPool;
+    private Connector[] _connectors;
+    private SessionIdManager _sessionIdManager;
+    private boolean _sendServerVersion = true; //send Server: header
+    private boolean _sendDateHeader = false; //send Date: header
+    private int _graceful=0;
+    private boolean _stopAtShutdown;
+    private boolean _dumpAfterStart=false;
+    private boolean _dumpBeforeStop=false;
+    private boolean _uncheckedPrintWriter=false;
+
+
+    /* ------------------------------------------------------------ */
+    public Server()
+    {
+        setServer(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link SelectChannelConnector} at the passed port.
+     */
+    public Server(int port)
+    {
+        setServer(this);
+
+        Connector connector=new SelectChannelConnector();
+        connector.setPort(port);
+        setConnectors(new Connector[]{connector});
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convenience constructor
+     * Creates server and a {@link SelectChannelConnector} at the passed address.
+     */
+    public Server(InetSocketAddress addr)
+    {
+        setServer(this);
+
+        Connector connector=new SelectChannelConnector();
+        connector.setHost(addr.getHostName());
+        connector.setPort(addr.getPort());
+        setConnectors(new Connector[]{connector});
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String getVersion()
+    {
+        return __version;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the container.
+     */
+    public Container getContainer()
+    {
+        return _container;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getStopAtShutdown()
+    {
+        return _stopAtShutdown;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStopAtShutdown(boolean stop)
+    {
+        //if we now want to stop
+        if (stop)
+        {
+            //and we weren't stopping before
+            if (!_stopAtShutdown)
+            {  
+                //only register to stop if we're already started (otherwise we'll do it in doStart())
+                if (isStarted()) 
+                    ShutdownThread.register(this);
+            }
+        }
+        else
+            ShutdownThread.deregister(this);
+        
+        _stopAtShutdown=stop;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the connectors.
+     */
+    public Connector[] getConnectors()
+    {
+        return _connectors;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addConnector(Connector connector)
+    {
+        setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
+     * remove a connector.
+     * @param connector The connector to remove.
+     */
+    public void removeConnector(Connector connector) {
+        setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the connectors for this server.
+     * Each connector has this server set as it's ThreadPool and its Handler.
+     * @param connectors The connectors to set.
+     */
+    public void setConnectors(Connector[] connectors)
+    {
+        if (connectors!=null)
+        {
+            for (int i=0;i<connectors.length;i++)
+                connectors[i].setServer(this);
+        }
+
+        _container.update(this, _connectors, connectors, "connector");
+        _connectors = connectors;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the threadPool.
+     */
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param threadPool The threadPool to set.
+     */
+    public void setThreadPool(ThreadPool threadPool)
+    {
+        if (_threadPool!=null)
+            removeBean(_threadPool);
+        _container.update(this, _threadPool, threadPool, "threadpool",false);
+        _threadPool = threadPool;
+        if (_threadPool!=null)
+            addBean(_threadPool);
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called after starting
+     */
+    public boolean isDumpAfterStart()
+    {
+        return _dumpAfterStart;
+    }
+
+    /**
+     * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
+     */
+    public void setDumpAfterStart(boolean dumpAfterStart)
+    {
+        _dumpAfterStart = dumpAfterStart;
+    }
+
+    /**
+     * @return true if {@link #dumpStdErr()} is called before stopping
+     */
+    public boolean isDumpBeforeStop()
+    {
+        return _dumpBeforeStop;
+    }
+
+    /**
+     * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
+     */
+    public void setDumpBeforeStop(boolean dumpBeforeStop)
+    {
+        _dumpBeforeStop = dumpBeforeStop;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (getStopAtShutdown())
+        {
+            ShutdownThread.register(this);    
+        }
+        
+        ShutdownMonitor.getInstance().start(); // initialize
+
+        LOG.info("jetty-"+__version);
+        HttpGenerator.setServerVersion(__version);
+        
+        MultiException mex=new MultiException();
+
+        if (_threadPool==null)
+            setThreadPool(new QueuedThreadPool());
+
+        try
+        {
+            super.doStart();
+        }
+        catch(Throwable e)
+        {
+            mex.add(e);
+        }
+
+        if (_connectors!=null && mex.size()==0)
+        {
+            for (int i=0;i<_connectors.length;i++)
+            {
+                try{_connectors[i].start();}
+                catch(Throwable e)
+                {
+                    mex.add(e);
+                }
+            }
+        }
+
+        if (isDumpAfterStart())
+            dumpStdErr();
+
+        mex.ifExceptionThrow();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (isDumpBeforeStop())
+            dumpStdErr();
+
+        MultiException mex=new MultiException();
+
+        if (_graceful>0)
+        {
+            if (_connectors!=null)
+            {
+                for (int i=_connectors.length;i-->0;)
+                {
+                    LOG.info("Graceful shutdown {}",_connectors[i]);
+                    try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
+                }
+            }
+
+            Handler[] contexts = getChildHandlersByClass(Graceful.class);
+            for (int c=0;c<contexts.length;c++)
+            {
+                Graceful context=(Graceful)contexts[c];
+                LOG.info("Graceful shutdown {}",context);
+                context.setShutdown(true);
+            }
+            Thread.sleep(_graceful);
+        }
+
+        if (_connectors!=null)
+        {
+            for (int i=_connectors.length;i-->0;)
+                try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
+        }
+
+        try {super.doStop(); } catch(Throwable e) { mex.add(e);}
+
+        mex.ifExceptionThrow();
+
+        if (getStopAtShutdown())
+            ShutdownThread.deregister(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handle(AbstractHttpConnection connection) throws IOException, ServletException
+    {
+        final String target=connection.getRequest().getPathInfo();
+        final Request request=connection.getRequest();
+        final Response response=connection.getResponse();
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("REQUEST "+target+" on "+connection);
+            handle(target, request, request, response);
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
+        }
+        else
+            handle(target, request, request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Handle a request from a connection.
+     * Called to handle a request on the connection when either the header has been received,
+     * or after the entire request has been received (for short requests of known length), or
+     * on the dispatch of an async request.
+     */
+    public void handleAsync(AbstractHttpConnection connection) throws IOException, ServletException
+    {
+        final AsyncContinuation async = connection.getRequest().getAsyncContinuation();
+        final AsyncContinuation.AsyncEventState state = async.getAsyncEventState();
+
+        final Request baseRequest=connection.getRequest();
+        final String path=state.getPath();
+
+        if (path!=null)
+        {
+            // this is a dispatch with a path
+            final String contextPath=state.getServletContext().getContextPath();
+            HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path));
+            baseRequest.setUri(uri);
+            baseRequest.setRequestURI(null);
+            baseRequest.setPathInfo(baseRequest.getRequestURI());
+            if (uri.getQuery()!=null)
+                baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8
+        }
+
+        final String target=baseRequest.getPathInfo();
+        final HttpServletRequest request=(HttpServletRequest)async.getRequest();
+        final HttpServletResponse response=(HttpServletResponse)async.getResponse();
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("REQUEST "+target+" on "+connection);
+            handle(target, baseRequest, request, response);
+            LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
+        }
+        else
+            handle(target, baseRequest, request, response);
+
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        getThreadPool().join();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionIdManager.
+     */
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionIdManager The sessionIdManager to set.
+     */
+    public void setSessionIdManager(SessionIdManager sessionIdManager)
+    {
+        if (_sessionIdManager!=null)
+            removeBean(_sessionIdManager);
+        _container.update(this, _sessionIdManager, sessionIdManager, "sessionIdManager",false);
+        _sessionIdManager = sessionIdManager;
+        if (_sessionIdManager!=null)
+            addBean(_sessionIdManager);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setSendServerVersion (boolean sendServerVersion)
+    {
+        _sendServerVersion = sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getSendServerVersion()
+    {
+        return _sendServerVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sendDateHeader
+     */
+    public void setSendDateHeader(boolean sendDateHeader)
+    {
+        _sendDateHeader = sendDateHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getSendDateHeader()
+    {
+        return _sendDateHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Deprecated
+    public int getMaxCookieVersion()
+    {
+        return 1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Deprecated
+    public void setMaxCookieVersion(int maxCookieVersion)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a LifeCycle object to be started/stopped
+     * along with the Server.
+     * @deprecated Use {@link #addBean(Object)}
+     * @param c
+     */
+    @Deprecated
+    public void addLifeCycle (LifeCycle c)
+    {
+        addBean(c);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an associated bean.
+     * The bean will be added to the servers {@link Container}
+     * and if it is a {@link LifeCycle} instance, it will be
+     * started/stopped along with the Server. Any beans that are also
+     * {@link Destroyable}, will be destroyed with the server.
+     * @param o the bean object to add
+     */
+    @Override
+    public boolean addBean(Object o)
+    {
+        if (super.addBean(o))
+        {
+            _container.addBean(o);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Remove a LifeCycle object to be started/stopped
+     * along with the Server
+     * @deprecated Use {@link #removeBean(Object)}
+     */
+    @Deprecated
+    public void removeLifeCycle (LifeCycle c)
+    {
+        removeBean(c);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove an associated bean.
+     */
+    @Override
+    public boolean removeBean (Object o)
+    {
+        if (super.removeBean(o))
+        {
+            _container.removeBean(o);
+            return true;
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#clearAttributes()
+     */
+    public void clearAttributes()
+    {
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#getAttributeNames()
+     */
+    public Enumeration getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        _attributes.setAttribute(name, attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the graceful
+     */
+    public int getGracefulShutdown()
+    {
+        return _graceful;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set graceful shutdown timeout.  If set, the internal <code>doStop()</code> method will not immediately stop the
+     * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
+     * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
+     * will be accepted, but existing requests can complete.  The server will then wait the configured timeout
+     * before stopping.
+     * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
+     *
+     */
+    public void setGracefulShutdown(int timeoutMS)
+    {
+        _graceful=timeoutMS;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out,indent,TypeUtil.asList(getHandlers()),getBeans(),TypeUtil.asList(_connectors));
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isUncheckedPrintWriter()
+    {
+        return _uncheckedPrintWriter;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setUncheckedPrintWriter(boolean unchecked)
+    {
+        _uncheckedPrintWriter=unchecked;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* A handler that can be gracefully shutdown.
+     * Called by doStop if a {@link #setGracefulShutdown} period is set.
+     * TODO move this somewhere better
+     */
+    public interface Graceful extends Handler
+    {
+        public void setShutdown(boolean shutdown);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void main(String...args) throws Exception
+    {
+        System.err.println(getVersion());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java b/src/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
new file mode 100644
index 0000000..1da30a3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java
@@ -0,0 +1,212 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Enumeration;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+
+/* ------------------------------------------------------------ */
+/** Class to tunnel a ServletRequest via a HttpServletRequest
+ */
+public class ServletRequestHttpWrapper extends ServletRequestWrapper implements HttpServletRequest
+{
+    public ServletRequestHttpWrapper(ServletRequest request)
+    {
+        super(request);
+    }
+
+    public String getAuthType()
+    {
+        return null;
+    }
+
+    public Cookie[] getCookies()
+    {
+        return null;
+    }
+
+    public long getDateHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaders(String name)
+    {
+        return null;
+    }
+
+    public Enumeration getHeaderNames()
+    {
+        return null;
+    }
+
+    public int getIntHeader(String name)
+    {
+        return 0;
+    }
+
+    public String getMethod()
+    {
+        return null;
+    }
+
+    public String getPathInfo()
+    {
+        return null;
+    }
+
+    public String getPathTranslated()
+    {
+        return null;
+    }
+
+    public String getContextPath()
+    {
+        return null;
+    }
+
+    public String getQueryString()
+    {
+        return null;
+    }
+
+    public String getRemoteUser()
+    {
+        return null;
+    }
+
+    public boolean isUserInRole(String role)
+    {
+        return false;
+    }
+
+    public Principal getUserPrincipal()
+    {
+        return null;
+    }
+
+    public String getRequestedSessionId()
+    {
+        return null;
+    }
+
+    public String getRequestURI()
+    {
+        return null;
+    }
+
+    public StringBuffer getRequestURL()
+    {
+        return null;
+    }
+
+    public String getServletPath()
+    {
+        return null;
+    }
+
+    public HttpSession getSession(boolean create)
+    {
+        return null;
+    }
+
+    public HttpSession getSession()
+    {
+        return null;
+    }
+
+    public boolean isRequestedSessionIdValid()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromCookie()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromURL()
+    {
+        return false;
+    }
+
+    public boolean isRequestedSessionIdFromUrl()
+    {
+        return false;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#authenticate(javax.servlet.http.HttpServletResponse)
+     */
+    public boolean authenticate(HttpServletResponse response) throws IOException, ServletException
+    {
+        return false;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#getPart(java.lang.String)
+     */
+    public Part getPart(String name) throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#getParts()
+     */
+    public Collection<Part> getParts() throws IOException, ServletException
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#login(java.lang.String, java.lang.String)
+     */
+    public void login(String username, String password) throws ServletException
+    {
+
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletRequest#logout()
+     */
+    public void logout() throws ServletException
+    {
+        
+    }
+
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java b/src/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
new file mode 100644
index 0000000..f98cd59
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ServletResponseHttpWrapper.java
@@ -0,0 +1,145 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.util.Collection;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+/* ------------------------------------------------------------ */
+/** Wrapper to tunnel a ServletResponse via a HttpServletResponse
+ */
+public class ServletResponseHttpWrapper extends ServletResponseWrapper implements HttpServletResponse
+{
+    public ServletResponseHttpWrapper(ServletResponse response)
+    {
+        super(response);
+    }
+
+    public void addCookie(Cookie cookie)
+    {        
+    }
+
+    public boolean containsHeader(String name)
+    {
+        return false;
+    }
+
+    public String encodeURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectURL(String url)
+    {
+        return null;
+    }
+
+    public String encodeUrl(String url)
+    {
+        return null;
+    }
+
+    public String encodeRedirectUrl(String url)
+    {
+        return null;
+    }
+
+    public void sendError(int sc, String msg) throws IOException
+    {        
+    }
+
+    public void sendError(int sc) throws IOException
+    {        
+    }
+
+    public void sendRedirect(String location) throws IOException
+    {        
+    }
+
+    public void setDateHeader(String name, long date)
+    {        
+    }
+
+    public void addDateHeader(String name, long date)
+    {        
+    }
+
+    public void setHeader(String name, String value)
+    {        
+    }
+
+    public void addHeader(String name, String value)
+    {        
+    }
+
+    public void setIntHeader(String name, int value)
+    {        
+    }
+
+    public void addIntHeader(String name, int value)
+    {        
+    }
+
+    public void setStatus(int sc)
+    {        
+    }
+
+    public void setStatus(int sc, String sm)
+    {        
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletResponse#getHeader(java.lang.String)
+     */
+    public String getHeader(String name)
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletResponse#getHeaderNames()
+     */
+    public Collection<String> getHeaderNames()
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletResponse#getHeaders(java.lang.String)
+     */
+    public Collection<String> getHeaders(String name)
+    {
+        return null;
+    }
+
+    /** 
+     * @see javax.servlet.http.HttpServletResponse#getStatus()
+     */
+    public int getStatus()
+    {
+        return 0;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/SessionIdManager.java b/src/java/org/eclipse/jetty/server/SessionIdManager.java
new file mode 100644
index 0000000..62a9304
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/SessionIdManager.java
@@ -0,0 +1,81 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/** Session ID Manager.
+ * Manages session IDs across multiple contexts.
+ */
+public interface SessionIdManager extends LifeCycle
+{
+    /**
+     * @param id The session ID without any cluster node extension
+     * @return True if the session ID is in use by at least one context.
+     */
+    public boolean idInUse(String id);
+    
+    /**
+     * Add a session to the list of known sessions for a given ID.
+     * @param session The session
+     */
+    public void addSession(HttpSession session);
+    
+    /**
+     * Remove session from the list of known sessions for a given ID.
+     * @param session
+     */
+    public void removeSession(HttpSession session);
+    
+    /**
+     * Call {@link HttpSession#invalidate()} on all known sessions for the given id.
+     * @param id The session ID without any cluster node extension
+     */
+    public void invalidateAll(String id);
+    
+    /**
+     * @param request
+     * @param created
+     * @return the new session id
+     */
+    public String newSessionId(HttpServletRequest request,long created);
+    
+    public String getWorkerName();
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get a cluster ID from a node ID.
+     * Strip node identifier from a located session ID.
+     * @param nodeId
+     * @return the cluster id
+     */
+    public String getClusterId(String nodeId);
+    
+    /* ------------------------------------------------------------ */
+    /** Get a node ID from a cluster ID and a request
+     * @param clusterId The ID of the session
+     * @param request The request that for the session (or null)
+     * @return The session ID qualified with the node ID.
+     */
+    public String getNodeId(String clusterId,HttpServletRequest request);
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/SessionManager.java b/src/java/org/eclipse/jetty/server/SessionManager.java
new file mode 100644
index 0000000..38e3f36
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/SessionManager.java
@@ -0,0 +1,307 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.util.EventListener;
+import java.util.Set;
+
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Session Manager.
+ * The API required to manage sessions for a servlet context.
+ *
+ */
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public interface SessionManager extends LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Session cookie name.
+     * Defaults to <code>JSESSIONID</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionCookie</code> context init parameter.
+     */
+    public final static String __SessionCookieProperty = "org.eclipse.jetty.servlet.SessionCookie";
+    public final static String __DefaultSessionCookie = "JSESSIONID";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session id path parameter name.
+     * Defaults to <code>jsessionid</code>, but can be set with the
+     * <code>org.eclipse.jetty.servlet.SessionIdPathParameterName</code> context init parameter.
+     * If set to null or "none" no URL rewriting will be done.
+     */
+    public final static String __SessionIdPathParameterNameProperty = "org.eclipse.jetty.servlet.SessionIdPathParameterName";
+    public final static String __DefaultSessionIdPathParameterName = "jsessionid";
+    public final static String __CheckRemoteSessionEncoding = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding";
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Domain.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the domain for session cookies. If it is not set, then
+     * no domain is specified for the session cookie.
+     */
+    public final static String __SessionDomainProperty = "org.eclipse.jetty.servlet.SessionDomain";
+    public final static String __DefaultSessionDomain = null;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Path.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the path for the session cookie.  If it is not set, then
+     * the context path is used as the path for the cookie.
+     */
+    public final static String __SessionPathProperty = "org.eclipse.jetty.servlet.SessionPath";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Session Max Age.
+     * If this property is set as a ServletContext InitParam, then it is
+     * used as the max age for the session cookie.  If it is not set, then
+     * a max age of -1 is used.
+     */
+    public final static String __MaxAgeProperty = "org.eclipse.jetty.servlet.MaxAge";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the <code>HttpSession</code> with the given session id
+     *
+     * @param id the session id
+     * @return the <code>HttpSession</code> with the corresponding id or null if no session with the given id exists
+     */
+    public HttpSession getHttpSession(String id);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates a new <code>HttpSession</code>.
+     *
+     * @param request the HttpServletRequest containing the requested session id
+     * @return the new <code>HttpSession</code>
+     */
+    public HttpSession newHttpSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookies should be HTTP-only (Microsoft extension)
+     * @see org.eclipse.jetty.http.HttpCookie#isHttpOnly()
+     */
+    public boolean getHttpOnly();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the max period of inactivity, after which the session is invalidated, in seconds.
+     * @see #setMaxInactiveInterval(int)
+     */
+    public int getMaxInactiveInterval();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the max period of inactivity, after which the session is invalidated, in seconds.
+     *
+     * @param seconds the max inactivity period, in seconds.
+     * @see #getMaxInactiveInterval()
+     */
+    public void setMaxInactiveInterval(int seconds);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the {@link SessionHandler}.
+     *
+     * @param handler the <code>SessionHandler</code> object
+     */
+    public void setSessionHandler(SessionHandler handler);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Adds an event listener for session-related events.
+     *
+     * @param listener the session event listener to add
+     *                 Individual SessionManagers implementations may accept arbitrary listener types,
+     *                 but they are expected to at least handle HttpSessionActivationListener,
+     *                 HttpSessionAttributeListener, HttpSessionBindingListener and HttpSessionListener.
+     * @see #removeEventListener(EventListener)
+     */
+    public void addEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an event listener for for session-related events.
+     *
+     * @param listener the session event listener to remove
+     * @see #addEventListener(EventListener)
+     */
+    public void removeEventListener(EventListener listener);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes all event listeners for session-related events.
+     *
+     * @see #removeEventListener(EventListener)
+     */
+    public void clearEventListeners();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Gets a Cookie for a session.
+     *
+     * @param session         the session to which the cookie should refer.
+     * @param contextPath     the context to which the cookie should be linked.
+     *                        The client will only send the cookie value when requesting resources under this path.
+     * @param requestIsSecure whether the client is accessing the server over a secure protocol (i.e. HTTPS).
+     * @return if this <code>SessionManager</code> uses cookies, then this method will return a new
+     *         {@link Cookie cookie object} that should be set on the client in order to link future HTTP requests
+     *         with the <code>session</code>. If cookies are not in use, this method returns <code>null</code>.
+     */
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @see #setSessionIdManager(SessionIdManager)
+     */
+    public SessionIdManager getSessionIdManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cross context session id manager.
+     * @deprecated use {@link #getSessionIdManager()}
+     */
+    @Deprecated
+    public SessionIdManager getMetaManager();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the cross context session id manager
+     *
+     * @param idManager the cross context session id manager.
+     * @see #getSessionIdManager()
+     */
+    public void setSessionIdManager(SessionIdManager idManager);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session to test for validity
+     * @return whether the given session is valid, that is, it has not been invalidated.
+     */
+    public boolean isValid(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster, extended with an optional node id.
+     * @see #getClusterId(HttpSession)
+     */
+    public String getNodeId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param session the session object
+     * @return the unique id of the session within the cluster (without a node id extension)
+     * @see #getNodeId(HttpSession)
+     */
+    public String getClusterId(HttpSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is first accessed by a request.
+     *
+     * @param session the session object
+     * @param secure  whether the request is secure or not
+     * @return the session cookie. If not null, this cookie should be set on the response to either migrate
+     *         the session or to refresh a session cookie that may expire.
+     * @see #complete(HttpSession)
+     */
+    public HttpCookie access(HttpSession session, boolean secure);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Called by the {@link SessionHandler} when a session is last accessed by a request.
+     *
+     * @param session the session object
+     * @see #access(HttpSession, boolean)
+     */
+    public void complete(HttpSession session);
+
+    /**
+     * Sets the session id URL path parameter name.
+     *
+     * @param parameterName the URL path parameter name for session id URL rewriting (null or "none" for no rewriting).
+     * @see #getSessionIdPathParameterName()
+     * @see #getSessionIdPathParameterNamePrefix()
+     */
+    public void setSessionIdPathParameterName(String parameterName);
+
+    /**
+     * @return the URL path parameter name for session id URL rewriting, by default "jsessionid".
+     * @see #setSessionIdPathParameterName(String)
+     */
+    public String getSessionIdPathParameterName();
+
+    /**
+     * @return a formatted version of {@link #getSessionIdPathParameterName()}, by default
+     *         ";" + sessionIdParameterName + "=", for easier lookup in URL strings.
+     * @see #getSessionIdPathParameterName()
+     */
+    public String getSessionIdPathParameterNamePrefix();
+
+    /**
+     * @return whether the session management is handled via cookies.
+     */
+    public boolean isUsingCookies();
+    
+    /**
+     * @return whether the session management is handled via URLs.
+     */
+    public boolean isUsingURLs();
+
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes();
+
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes();
+
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes);
+
+    public SessionCookieConfig getSessionCookieConfig();
+    
+    /**
+     * @return True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public boolean isCheckingRemoteSessionIdEncoding();
+    
+    /**
+     * @param remote True if absolute URLs are check for remoteness before being session encoded.
+     */
+    public void setCheckingRemoteSessionIdEncoding(boolean remote);
+}
diff --git a/src/java/org/eclipse/jetty/server/ShutdownMonitor.java b/src/java/org/eclipse/jetty/server/ShutdownMonitor.java
new file mode 100644
index 0000000..36d3cd7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ShutdownMonitor.java
@@ -0,0 +1,384 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor 
+{
+    // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+    static class Holder
+    {
+        static ShutdownMonitor instance = new ShutdownMonitor();
+    }
+
+    public static ShutdownMonitor getInstance()
+    {
+        return Holder.instance;
+    }
+
+    /**
+     * ShutdownMonitorThread
+     *
+     * Thread for listening to STOP.PORT for command to stop Jetty.
+     * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+     * called after the stop.
+     *
+     */
+    public class ShutdownMonitorThread extends Thread
+    {
+
+        public ShutdownMonitorThread ()
+        {
+            setDaemon(true);
+            setName("ShutdownMonitor");
+        }
+        
+        @Override
+        public void run()
+        {
+            if (serverSocket == null)
+            {
+                return;
+            }
+
+            while (serverSocket != null)
+            {
+                Socket socket = null;
+                try
+                {
+                    socket = serverSocket.accept();
+
+                    LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+                    String receivedKey = lin.readLine();
+                    if (!key.equals(receivedKey))
+                    {
+                        System.err.println("Ignoring command with incorrect key");
+                        continue;
+                    }
+
+                    OutputStream out = socket.getOutputStream();
+
+                    String cmd = lin.readLine();
+                    debug("command=%s",cmd);
+                    if ("stop".equals(cmd))
+                    {
+                        // Graceful Shutdown
+                        debug("Issuing graceful shutdown..");
+                        ShutdownThread.getInstance().run();
+
+                        // Reply to client
+                        debug("Informing client that we are stopped.");
+                        out.write("Stopped\r\n".getBytes(StringUtil.__UTF8));
+                        out.flush();
+
+                        // Shutdown Monitor
+                        debug("Shutting down monitor");
+                        close(socket);
+                        socket = null;
+                        close(serverSocket);
+                        serverSocket = null;
+
+                        if (exitVm)
+                        {
+                            // Kill JVM
+                            debug("Killing JVM");
+                            System.exit(0);
+                        }
+                    }
+                    else if ("status".equals(cmd))
+                    {
+                        // Reply to client
+                        out.write("OK\r\n".getBytes(StringUtil.__UTF8));
+                        out.flush();
+                    }
+                }
+                catch (Exception e)
+                {
+                    debug(e);
+                    System.err.println(e.toString());
+                }
+                finally
+                {
+                    close(socket);
+                    socket = null;
+                }
+            }
+        }
+        
+        public void start()
+        {
+            if (isAlive())
+            {
+                System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+
+            startListenSocket();
+            
+            if (serverSocket == null)
+            {
+                return;
+            }
+            if (DEBUG)
+                System.err.println("Starting ShutdownMonitorThread");
+            super.start();
+        }
+        
+        private void startListenSocket()
+        {
+            if (port < 0)
+            {            
+                if (DEBUG)
+                    System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+                return;
+            }
+
+            try
+            {
+                serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+                if (port == 0)
+                {
+                    // server assigned port in use
+                    port = serverSocket.getLocalPort();
+                    System.out.printf("STOP.PORT=%d%n",port);
+                }
+
+                if (key == null)
+                {
+                    // create random key
+                    key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+                    System.out.printf("STOP.KEY=%s%n",key);
+                }
+            }
+            catch (Exception e)
+            {
+                debug(e);
+                System.err.println("Error binding monitor port " + port + ": " + e.toString());
+                serverSocket = null;
+            }
+            finally
+            {
+                // establish the port and key that are in use
+                debug("STOP.PORT=%d",port);
+                debug("STOP.KEY=%s",key);
+                debug("%s",serverSocket);
+            }
+        }
+
+    }
+    
+    private boolean DEBUG;
+    private int port;
+    private String key;
+    private boolean exitVm;
+    private ServerSocket serverSocket;
+    private ShutdownMonitorThread thread;
+    
+    
+
+    /**
+     * Create a ShutdownMonitor using configuration from the System properties.
+     * <p>
+     * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+     * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+     * <p>
+     * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+     */
+    private ShutdownMonitor()
+    {
+        Properties props = System.getProperties();
+
+        this.DEBUG = props.containsKey("DEBUG");
+
+        // Use values passed thru via /jetty-start/
+        this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+        this.key = props.getProperty("STOP.KEY",null);
+        this.exitVm = true;
+    }
+
+    private void close(ServerSocket server)
+    {
+        if (server == null)
+        {
+            return;
+        }
+
+        try
+        {
+            server.close();
+        }
+        catch (IOException ignore)
+        {
+            /* ignore */
+        }
+    }
+
+    private void close(Socket socket)
+    {
+        if (socket == null)
+        {
+            return;
+        }
+
+        try
+        {
+            socket.close();
+        }
+        catch (IOException ignore)
+        {
+            /* ignore */
+        }
+    }
+
+    private void debug(String format, Object... args)
+    {
+        if (DEBUG)
+        {
+            System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+        }
+    }
+
+    private void debug(Throwable t)
+    {
+        if (DEBUG)
+        {
+            t.printStackTrace(System.err);
+        }
+    }
+
+    public String getKey()
+    {
+        return key;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    public ServerSocket getServerSocket()
+    {
+        return serverSocket;
+    }
+
+    public boolean isExitVm()
+    {
+        return exitVm;
+    }
+
+
+    public void setDebug(boolean flag)
+    {
+        this.DEBUG = flag;
+    }
+
+    public void setExitVm(boolean exitVm)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.exitVm = exitVm;
+        }
+    }
+
+    public void setKey(String key)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.key = key;
+        }
+    }
+
+    public void setPort(int port)
+    {
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                throw new IllegalStateException("ShutdownMonitorThread already started");
+            }
+            this.port = port;
+        }
+    }
+
+    protected void start() throws Exception
+    {
+        ShutdownMonitorThread t = null;
+        synchronized (this)
+        {
+            if (thread != null && thread.isAlive())
+            {
+                System.err.printf("ShutdownMonitorThread already started");
+                return; // cannot start it again
+            }
+         
+            thread = new ShutdownMonitorThread();
+            t = thread;
+        }
+         
+        if (t != null)
+            t.start();
+    }
+
+
+    protected boolean isAlive ()
+    {
+        boolean result = false;
+        synchronized (this)
+        {
+            result = (thread != null && thread.isAlive());
+        }
+        return result;
+    }
+    
+ 
+    @Override
+    public String toString()
+    {
+        return String.format("%s[port=%d]",this.getClass().getName(),port);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/UserIdentity.java b/src/java/org/eclipse/jetty/server/UserIdentity.java
new file mode 100644
index 0000000..4ebb5fc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/UserIdentity.java
@@ -0,0 +1,116 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server;
+import java.security.Principal;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+
+/* ------------------------------------------------------------ */
+/** User object that encapsulates user identity and operations such as run-as-role actions, 
+ * checking isUserInRole and getUserPrincipal.
+ *
+ * Implementations of UserIdentity should be immutable so that they may be
+ * cached by Authenticators and LoginServices.
+ *
+ */
+public interface UserIdentity
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user subject
+     */
+    Subject getSubject();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The user principal
+     */
+    Principal getUserPrincipal();
+
+    /* ------------------------------------------------------------ */
+    /** Check if the user is in a role.
+     * This call is used to satisfy authorization calls from 
+     * container code which will be using translated role names.
+     * @param role A role name.
+     * @param scope
+     * @return True if the user can act in that role.
+     */
+    boolean isUserInRole(String role, Scope scope);
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * A UserIdentity Scope.
+     * A scope is the environment in which a User Identity is to 
+     * be interpreted. Typically it is set by the target servlet of 
+     * a request.
+     */
+    interface Scope
+    {
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The context path that the identity is being considered within
+         */
+        String getContextPath();
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @return The name of the identity context. Typically this is the servlet name.
+         */
+        String getName();
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @return A map of role reference names that converts from names used by application code
+         * to names used by the context deployment.
+         */
+        Map<String,String> getRoleRefMap();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public interface UnauthenticatedUserIdentity extends UserIdentity
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public static final UserIdentity UNAUTHENTICATED_IDENTITY = new UnauthenticatedUserIdentity()
+    {
+        public Subject getSubject()
+        {
+            return null;
+        }
+        
+        public Principal getUserPrincipal()
+        {
+            return null;
+        }
+        
+        public boolean isUserInRole(String role, Scope scope)
+        {
+            return false;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return "UNAUTHENTICATED";
+        }
+    };
+}
diff --git a/src/java/org/eclipse/jetty/server/bio/SocketConnector.java b/src/java/org/eclipse/jetty/server/bio/SocketConnector.java
new file mode 100644
index 0000000..337af11
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/bio/SocketConnector.java
@@ -0,0 +1,325 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.bio;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.BlockingHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------------------------- */
+/**  Socket Connector.
+ * This connector implements a traditional blocking IO and threading model.
+ * Normal JRE sockets are used and a thread is allocated per connection.
+ * Buffers are managed so that large buffers are only allocated to active connections.
+ *
+ * This Connector should only be used if NIO is not available.
+ *
+ * @org.apache.xbean.XBean element="bioConnector" description="Creates a BIO based socket connector"
+ *
+ *
+ */
+public class SocketConnector extends AbstractConnector
+{
+    private static final Logger LOG = Log.getLogger(SocketConnector.class);
+
+    protected ServerSocket _serverSocket;
+    protected final Set<EndPoint> _connections;
+    protected volatile int _localPort=-1;
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     *
+     */
+    public SocketConnector()
+    {
+        _connections=new HashSet<EndPoint>();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getConnection()
+    {
+        return _serverSocket;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void open() throws IOException
+    {
+        // Create a new server socket and set to non blocking mode
+        if (_serverSocket==null || _serverSocket.isClosed())
+        _serverSocket= newServerSocket(getHost(),getPort(),getAcceptQueueSize());
+        _serverSocket.setReuseAddress(getReuseAddress());
+        _localPort=_serverSocket.getLocalPort();
+        if (_localPort<=0)
+            throw new IllegalStateException("port not allocated for "+this);
+
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
+    {
+        ServerSocket ss= host==null?
+            new ServerSocket(port,backlog):
+            new ServerSocket(port,backlog,InetAddress.getByName(host));
+
+        return ss;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void close() throws IOException
+    {
+        if (_serverSocket!=null)
+            _serverSocket.close();
+        _serverSocket=null;
+        _localPort=-2;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void accept(int acceptorID)
+    	throws IOException, InterruptedException
+    {
+        Socket socket = _serverSocket.accept();
+        configure(socket);
+
+        ConnectorEndPoint connection=new ConnectorEndPoint(socket);
+        connection.dispatch();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Allows subclass to override Conection if required.
+     */
+    protected Connection newConnection(EndPoint endpoint)
+    {
+        return new BlockingHttpConnection(this, endpoint, getServer());
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void customize(EndPoint endpoint, Request request)
+        throws IOException
+    {
+        ConnectorEndPoint connection = (ConnectorEndPoint)endpoint;
+        int lrmit = isLowResources()?_lowResourceMaxIdleTime:_maxIdleTime;
+        connection.setMaxIdleTime(lrmit);
+
+        super.customize(endpoint, request);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public int getLocalPort()
+    {
+        return _localPort;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _connections.clear();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        Set<EndPoint> set = new HashSet<EndPoint>();
+        synchronized(_connections)
+        {
+            set.addAll(_connections);
+        }
+        for (EndPoint endPoint : set)
+        {
+            ConnectorEndPoint connection = (ConnectorEndPoint)endPoint;
+            connection.close();
+        }
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        super.dump(out, indent);
+        Set<EndPoint> connections = new HashSet<EndPoint>();
+        synchronized (_connections)
+        {
+            connections.addAll(_connections);
+        }
+        AggregateLifeCycle.dump(out, indent, connections);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    protected class ConnectorEndPoint extends SocketEndPoint implements Runnable, ConnectedEndPoint
+    {
+        volatile Connection _connection;
+        protected final Socket _socket;
+
+        public ConnectorEndPoint(Socket socket) throws IOException
+        {
+            super(socket,_maxIdleTime);
+            _connection = newConnection(this);
+            _socket=socket;
+        }
+
+        public Connection getConnection()
+        {
+            return _connection;
+        }
+
+        public void setConnection(Connection connection)
+        {
+            if (_connection!=connection && _connection!=null)
+                connectionUpgraded(_connection,connection);
+            _connection=connection;
+        }
+
+        public void dispatch() throws IOException
+        {
+            if (getThreadPool()==null || !getThreadPool().dispatch(this))
+            {
+                LOG.warn("dispatch failed for {}",_connection);
+                close();
+            }
+        }
+
+        @Override
+        public int fill(Buffer buffer) throws IOException
+        {
+            int l = super.fill(buffer);
+            if (l<0)
+            {
+                if (!isInputShutdown())
+                    shutdownInput();
+                if (isOutputShutdown())
+                    close();
+            }
+            return l;
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            if (_connection instanceof AbstractHttpConnection)
+                ((AbstractHttpConnection)_connection).getRequest().getAsyncContinuation().cancel();
+            super.close();
+        }
+
+        public void run()
+        {
+            try
+            {
+                connectionOpened(_connection);
+                synchronized(_connections)
+                {
+                    _connections.add(this);
+                }
+
+                while (isStarted() && !isClosed())
+                {
+                    if (_connection.isIdle())
+                    {
+                        if (isLowResources())
+                            setMaxIdleTime(getLowResourcesMaxIdleTime());
+                    }
+
+                    _connection=_connection.handle();
+                }
+            }
+            catch (EofException e)
+            {
+                LOG.debug("EOF", e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch (SocketException e)
+            {
+                LOG.debug("EOF", e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch (HttpException e)
+            {
+                LOG.debug("BAD", e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch(Exception e)
+            {
+                LOG.warn("handle failed?",e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            finally
+            {
+                connectionClosed(_connection);
+                synchronized(_connections)
+                {
+                    _connections.remove(this);
+                }
+
+                // wait for client to close, but if not, close ourselves.
+                try
+                {
+                    if (!_socket.isClosed())
+                    {
+                        long timestamp=System.currentTimeMillis();
+                        int max_idle=getMaxIdleTime();
+
+                        _socket.setSoTimeout(getMaxIdleTime());
+                        int c=0;
+                        do
+                        {
+                            c = _socket.getInputStream().read();
+                        }
+                        while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle);
+                        if (!_socket.isClosed())
+                            _socket.close();
+                    }
+                }
+                catch(IOException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/src/java/org/eclipse/jetty/server/handler/AbstractHandler.java
new file mode 100644
index 0000000..07f140c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/AbstractHandler.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** AbstractHandler.
+ * 
+ *
+ */
+public abstract class AbstractHandler extends AggregateLifeCycle implements Handler
+{
+    private static final Logger LOG = Log.getLogger(AbstractHandler.class);
+
+    private Server _server;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
+    public AbstractHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#start()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        LOG.debug("starting {}",this);
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.thread.LifeCycle#stop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        LOG.debug("stopping {}",this);
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setServer(Server server)
+    {
+        Server old_server=_server;
+        if (old_server!=null && old_server!=server)
+            old_server.getContainer().removeBean(this);
+        _server=server;
+        if (_server!=null && _server!=old_server)
+            _server.getContainer().addBean(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Server getServer()
+    {
+        return _server;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        super.destroy();
+        if (_server!=null)
+            _server.getContainer().removeBean(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dumpThis(Appendable out) throws IOException
+    {
+        out.append(toString()).append(" - ").append(getState()).append('\n');
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java b/src/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
new file mode 100644
index 0000000..55e33f6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/AbstractHandlerContainer.java
@@ -0,0 +1,123 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+
+import java.io.IOException;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.TypeUtil;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Handler Container.
+ * This is the base class for handlers that may contain other handlers.
+ *
+ */
+public abstract class AbstractHandlerContainer extends AbstractHandler implements HandlerContainer
+{
+    /* ------------------------------------------------------------ */
+    public AbstractHandlerContainer()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Handler[] getChildHandlers()
+    {
+        Object list = expandChildren(null,null);
+        return (Handler[])LazyList.toArray(list, Handler.class);
+    }
+        
+    /* ------------------------------------------------------------ */
+    public Handler[] getChildHandlersByClass(Class<?> byclass)
+    {
+        Object list = expandChildren(null,byclass);
+        return (Handler[])LazyList.toArray(list, byclass);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public <T extends Handler> T getChildHandlerByClass(Class<T> byclass)
+    {
+        // TODO this can be more efficient?
+        Object list = expandChildren(null,byclass);
+        if (list==null)
+            return null;
+        return (T)LazyList.get(list, 0);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected Object expandChildren(Object list, Class<?> byClass)
+    {
+        return list;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Object expandHandler(Handler handler, Object list, Class<Handler> byClass)
+    {
+        if (handler==null)
+            return list;
+        
+        if (byClass==null || byClass.isAssignableFrom(handler.getClass()))
+            list=LazyList.add(list, handler);
+
+        if (handler instanceof AbstractHandlerContainer)
+            list=((AbstractHandlerContainer)handler).expandChildren(list, byClass);
+        else if (handler instanceof HandlerContainer)
+        {
+            HandlerContainer container = (HandlerContainer)handler;
+            Handler[] handlers=byClass==null?container.getChildHandlers():container.getChildHandlersByClass(byClass);
+            list=LazyList.addArray(list, handlers);
+        }
+        
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static <T extends HandlerContainer> T findContainerOf(HandlerContainer root,Class<T>type, Handler handler)
+    {
+        if (root==null || handler==null)
+            return null;
+        
+        Handler[] branches=root.getChildHandlersByClass(type);
+        if (branches!=null)
+        {
+            for (Handler h:branches)
+            {
+                T container = (T)h;
+                Handler[] candidates = container.getChildHandlersByClass(handler.getClass());
+                if (candidates!=null)
+                {
+                    for (Handler c:candidates)
+                        if (c==handler)
+                            return container;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out,indent,getBeans(),TypeUtil.asList(getHandlers()));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ConnectHandler.java b/src/java/org/eclipse/jetty/server/handler/ConnectHandler.java
new file mode 100644
index 0000000..ac70203
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ConnectHandler.java
@@ -0,0 +1,1025 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.HostMap;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/**
+ * <p>Implementation of a tunneling proxy that supports HTTP CONNECT.</p>
+ * <p>To work as CONNECT proxy, objects of this class must be instantiated using the no-arguments
+ * constructor, since the remote server information will be present in the CONNECT URI.</p>
+ */
+public class ConnectHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ConnectHandler.class);
+    private final SelectorManager _selectorManager = new Manager();
+    private volatile int _connectTimeout = 5000;
+    private volatile int _writeTimeout = 30000;
+    private volatile ThreadPool _threadPool;
+    private volatile boolean _privateThreadPool;
+    private HostMap<String> _white = new HostMap<String>();
+    private HostMap<String> _black = new HostMap<String>();
+
+    public ConnectHandler()
+    {
+        this(null);
+    }
+
+    public ConnectHandler(String[] white, String[] black)
+    {
+        this(null, white, black);
+    }
+
+    public ConnectHandler(Handler handler)
+    {
+        setHandler(handler);
+    }
+
+    public ConnectHandler(Handler handler, String[] white, String[] black)
+    {
+        setHandler(handler);
+        set(white, _white);
+        set(black, _black);
+    }
+
+    /**
+     * @return the timeout, in milliseconds, to connect to the remote server
+     */
+    public int getConnectTimeout()
+    {
+        return _connectTimeout;
+    }
+
+    /**
+     * @param connectTimeout the timeout, in milliseconds, to connect to the remote server
+     */
+    public void setConnectTimeout(int connectTimeout)
+    {
+        _connectTimeout = connectTimeout;
+    }
+
+    /**
+     * @return the timeout, in milliseconds, to write data to a peer
+     */
+    public int getWriteTimeout()
+    {
+        return _writeTimeout;
+    }
+
+    /**
+     * @param writeTimeout the timeout, in milliseconds, to write data to a peer
+     */
+    public void setWriteTimeout(int writeTimeout)
+    {
+        _writeTimeout = writeTimeout;
+    }
+
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+
+        server.getContainer().update(this, null, _selectorManager, "selectManager");
+
+        if (_privateThreadPool)
+            server.getContainer().update(this, null, _privateThreadPool, "threadpool", true);
+        else
+            _threadPool = server.getThreadPool();
+    }
+
+    /**
+     * @return the thread pool
+     */
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /**
+     * @param threadPool the thread pool
+     */
+    public void setThreadPool(ThreadPool threadPool)
+    {
+        if (getServer() != null)
+            getServer().getContainer().update(this, _privateThreadPool ? _threadPool : null, threadPool, "threadpool", true);
+        _privateThreadPool = threadPool != null;
+        _threadPool = threadPool;
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+
+        if (_threadPool == null)
+        {
+            _threadPool = getServer().getThreadPool();
+            _privateThreadPool = false;
+        }
+        if (_threadPool instanceof LifeCycle && !((LifeCycle)_threadPool).isRunning())
+            ((LifeCycle)_threadPool).start();
+
+        _selectorManager.start();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        _selectorManager.stop();
+
+        ThreadPool threadPool = _threadPool;
+        if (_privateThreadPool && _threadPool != null && threadPool instanceof LifeCycle)
+            ((LifeCycle)threadPool).stop();
+
+        super.doStop();
+    }
+
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (HttpMethods.CONNECT.equalsIgnoreCase(request.getMethod()))
+        {
+            LOG.debug("CONNECT request for {}", request.getRequestURI());
+            try
+            {
+                handleConnect(baseRequest, request, response, request.getRequestURI());
+            }
+            catch(Exception e)
+            {
+                LOG.warn("ConnectHandler "+baseRequest.getUri()+" "+ e);
+                LOG.debug(e);
+            }
+        }
+        else
+        {
+            super.handle(target, baseRequest, request, response);
+        }
+    }
+
+    /**
+     * <p>Handles a CONNECT request.</p>
+     * <p>CONNECT requests may have authentication headers such as <code>Proxy-Authorization</code>
+     * that authenticate the client with the proxy.</p>
+     *
+     * @param baseRequest   Jetty-specific http request
+     * @param request       the http request
+     * @param response      the http response
+     * @param serverAddress the remote server address in the form {@code host:port}
+     * @throws ServletException if an application error occurs
+     * @throws IOException      if an I/O error occurs
+     */
+    protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress) throws ServletException, IOException
+    {
+        boolean proceed = handleAuthentication(request, response, serverAddress);
+        if (!proceed)
+            return;
+
+        String host = serverAddress;
+        int port = 80;
+        int colon = serverAddress.indexOf(':');
+        if (colon > 0)
+        {
+            host = serverAddress.substring(0, colon);
+            port = Integer.parseInt(serverAddress.substring(colon + 1));
+        }
+
+        if (!validateDestination(host))
+        {
+            LOG.info("ProxyHandler: Forbidden destination " + host);
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            baseRequest.setHandled(true);
+            return;
+        }
+
+        SocketChannel channel;
+
+        try
+        {
+            channel = connectToServer(request,host,port);
+        }
+        catch (SocketException se)
+        {
+            LOG.info("ConnectHandler: SocketException " + se.getMessage());
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            baseRequest.setHandled(true);
+            return;
+        }
+        catch (SocketTimeoutException ste)
+        {
+            LOG.info("ConnectHandler: SocketTimeoutException" + ste.getMessage());
+            response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
+            baseRequest.setHandled(true);
+            return;
+        }
+        catch (IOException ioe)
+        {
+            LOG.info("ConnectHandler: IOException" + ioe.getMessage());
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            baseRequest.setHandled(true);
+            return;
+        }
+
+        // Transfer unread data from old connection to new connection
+        // We need to copy the data to avoid races:
+        // 1. when this unread data is written and the server replies before the clientToProxy
+        // connection is installed (it is only installed after returning from this method)
+        // 2. when the client sends data before this unread data has been written.
+        AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection();
+        Buffer headerBuffer = ((HttpParser)httpConnection.getParser()).getHeaderBuffer();
+        Buffer bodyBuffer = ((HttpParser)httpConnection.getParser()).getBodyBuffer();
+        int length = headerBuffer == null ? 0 : headerBuffer.length();
+        length += bodyBuffer == null ? 0 : bodyBuffer.length();
+        IndirectNIOBuffer buffer = null;
+        if (length > 0)
+        {
+            buffer = new IndirectNIOBuffer(length);
+            if (headerBuffer != null)
+            {
+                buffer.put(headerBuffer);
+                headerBuffer.clear();
+            }
+            if (bodyBuffer != null)
+            {
+                buffer.put(bodyBuffer);
+                bodyBuffer.clear();
+            }
+        }
+
+        ConcurrentMap<String, Object> context = new ConcurrentHashMap<String, Object>();
+        prepareContext(request, context);
+
+        ClientToProxyConnection clientToProxy = prepareConnections(context, channel, buffer);
+
+        // CONNECT expects a 200 response
+        response.setStatus(HttpServletResponse.SC_OK);
+
+        // Prevent close
+        baseRequest.getConnection().getGenerator().setPersistent(true);
+
+        // Close to force last flush it so that the client receives it
+        response.getOutputStream().close();
+
+        upgradeConnection(request, response, clientToProxy);
+    }
+
+    private ClientToProxyConnection prepareConnections(ConcurrentMap<String, Object> context, SocketChannel channel, Buffer buffer)
+    {
+        AbstractHttpConnection httpConnection = AbstractHttpConnection.getCurrentConnection();
+        ProxyToServerConnection proxyToServer = newProxyToServerConnection(context, buffer);
+        ClientToProxyConnection clientToProxy = newClientToProxyConnection(context, channel, httpConnection.getEndPoint(), httpConnection.getTimeStamp());
+        clientToProxy.setConnection(proxyToServer);
+        proxyToServer.setConnection(clientToProxy);
+        return clientToProxy;
+    }
+
+    /**
+     * <p>Handles the authentication before setting up the tunnel to the remote server.</p>
+     * <p>The default implementation returns true.</p>
+     *
+     * @param request  the HTTP request
+     * @param response the HTTP response
+     * @param address  the address of the remote server in the form {@code host:port}.
+     * @return true to allow to connect to the remote host, false otherwise
+     * @throws ServletException to report a server error to the caller
+     * @throws IOException      to report a server error to the caller
+     */
+    protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException
+    {
+        return true;
+    }
+
+    protected ClientToProxyConnection newClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timeStamp)
+    {
+        return new ClientToProxyConnection(context, channel, endPoint, timeStamp);
+    }
+
+    protected ProxyToServerConnection newProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer buffer)
+    {
+        return new ProxyToServerConnection(context, buffer);
+    }
+
+    // may return null
+    private SocketChannel connectToServer(HttpServletRequest request, String host, int port) throws IOException
+    {
+        SocketChannel channel = connect(request, host, port);      
+        channel.configureBlocking(false);
+        return channel;
+    }
+
+    /**
+     * <p>Establishes a connection to the remote server.</p>
+     *
+     * @param request the HTTP request that initiated the tunnel
+     * @param host    the host to connect to
+     * @param port    the port to connect to
+     * @return a {@link SocketChannel} connected to the remote server
+     * @throws IOException if the connection cannot be established
+     */
+    protected SocketChannel connect(HttpServletRequest request, String host, int port) throws IOException
+    {
+        SocketChannel channel = SocketChannel.open();
+
+        if (channel == null)
+        {
+            throw new IOException("unable to connect to " + host + ":" + port);
+        }
+
+        try
+        {
+            // Connect to remote server
+            LOG.debug("Establishing connection to {}:{}", host, port);
+            channel.socket().setTcpNoDelay(true);
+            channel.socket().connect(new InetSocketAddress(host, port), getConnectTimeout());
+            LOG.debug("Established connection to {}:{}", host, port);
+            return channel;
+        }
+        catch (IOException x)
+        {
+            LOG.debug("Failed to establish connection to " + host + ":" + port, x);
+            try
+            {
+                channel.close();
+            }
+            catch (IOException xx)
+            {
+                LOG.ignore(xx);
+            }
+            throw x;
+        }
+    }
+
+    protected void prepareContext(HttpServletRequest request, ConcurrentMap<String, Object> context)
+    {
+    }
+
+    private void upgradeConnection(HttpServletRequest request, HttpServletResponse response, Connection connection) throws IOException
+    {
+        // Set the new connection as request attribute and change the status to 101
+        // so that Jetty understands that it has to upgrade the connection
+        request.setAttribute("org.eclipse.jetty.io.Connection", connection);
+        response.setStatus(HttpServletResponse.SC_SWITCHING_PROTOCOLS);
+        LOG.debug("Upgraded connection to {}", connection);
+    }
+
+    private void register(SocketChannel channel, ProxyToServerConnection proxyToServer) throws IOException
+    {
+        _selectorManager.register(channel, proxyToServer);
+        proxyToServer.waitReady(_connectTimeout);
+    }
+
+    /**
+     * <p>Reads (with non-blocking semantic) into the given {@code buffer} from the given {@code endPoint}.</p>
+     *
+     * @param endPoint the endPoint to read from
+     * @param buffer   the buffer to read data into
+     * @param context  the context information related to the connection
+     * @return the number of bytes read (possibly 0 since the read is non-blocking)
+     *         or -1 if the channel has been closed remotely
+     * @throws IOException if the endPoint cannot be read
+     */
+    protected int read(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
+    {
+        return endPoint.fill(buffer);
+    }
+
+    /**
+     * <p>Writes (with blocking semantic) the given buffer of data onto the given endPoint.</p>
+     *
+     * @param endPoint the endPoint to write to
+     * @param buffer   the buffer to write
+     * @param context  the context information related to the connection
+     * @throws IOException if the buffer cannot be written
+     * @return the number of bytes written
+     */
+    protected int write(EndPoint endPoint, Buffer buffer, ConcurrentMap<String, Object> context) throws IOException
+    {
+        if (buffer == null)
+            return 0;
+
+        int length = buffer.length();
+        final StringBuilder debug = LOG.isDebugEnabled()?new StringBuilder():null;
+        int flushed = endPoint.flush(buffer);
+        if (debug!=null)
+            debug.append(flushed);
+        
+        // Loop until all written
+        while (buffer.length()>0 && !endPoint.isOutputShutdown())
+        {
+            if (!endPoint.isBlocking())
+            {
+                boolean ready = endPoint.blockWritable(getWriteTimeout());
+                if (!ready)
+                    throw new IOException("Write timeout");
+            }
+            flushed = endPoint.flush(buffer);
+            if (debug!=null)
+                debug.append("+").append(flushed);
+        }
+       
+        LOG.debug("Written {}/{} bytes {}", debug, length, endPoint);
+        buffer.compact();
+        return length;
+    }
+
+    private class Manager extends SelectorManager
+    {
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
+        {
+            SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, key, channel.socket().getSoTimeout());
+            endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
+            endp.setMaxIdleTime(_writeTimeout);
+            return endp;
+        }
+
+        @Override
+        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
+        {
+            ProxyToServerConnection proxyToServer = (ProxyToServerConnection)attachment;
+            proxyToServer.setTimeStamp(System.currentTimeMillis());
+            proxyToServer.setEndPoint(endpoint);
+            return proxyToServer;
+        }
+
+        @Override
+        protected void endPointOpened(SelectChannelEndPoint endpoint)
+        {
+            ProxyToServerConnection proxyToServer = (ProxyToServerConnection)endpoint.getSelectionKey().attachment();
+            proxyToServer.ready();
+        }
+
+        @Override
+        public boolean dispatch(Runnable task)
+        {
+            return _threadPool.dispatch(task);
+        }
+
+        @Override
+        protected void endPointClosed(SelectChannelEndPoint endpoint)
+        {
+        }
+
+        @Override
+        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
+        {
+        }
+    }
+
+    public class ProxyToServerConnection implements AsyncConnection
+    {
+        private final CountDownLatch _ready = new CountDownLatch(1);
+        private final Buffer _buffer = new IndirectNIOBuffer(4096);
+        private final ConcurrentMap<String, Object> _context;
+        private volatile Buffer _data;
+        private volatile ClientToProxyConnection _toClient;
+        private volatile long _timestamp;
+        private volatile AsyncEndPoint _endPoint;
+
+        public ProxyToServerConnection(ConcurrentMap<String, Object> context, Buffer data)
+        {
+            _context = context;
+            _data = data;
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder builder = new StringBuilder("ProxyToServer");
+            builder.append("(:").append(_endPoint.getLocalPort());
+            builder.append("<=>:").append(_endPoint.getRemotePort());
+            return builder.append(")").toString();
+        }
+
+        public Connection handle() throws IOException
+        {
+            LOG.debug("{}: begin reading from server", this);
+            try
+            {
+                writeData();
+
+                while (true)
+                {
+                    int read = read(_endPoint, _buffer, _context);
+
+                    if (read == -1)
+                    {
+                        LOG.debug("{}: server closed connection {}", this, _endPoint);
+
+                        if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
+                            closeClient();
+                        else
+                            _toClient.shutdownOutput();
+
+                        break;
+                    }
+
+                    if (read == 0)
+                        break;
+
+                    LOG.debug("{}: read from server {} bytes {}", this, read, _endPoint);
+                    int written = write(_toClient._endPoint, _buffer, _context);
+                    LOG.debug("{}: written to {} {} bytes", this, _toClient, written);
+                }
+                return this;
+            }
+            catch (ClosedChannelException x)
+            {
+                LOG.debug(x);
+                throw x;
+            }
+            catch (IOException x)
+            {
+                LOG.warn(this + ": unexpected exception", x);
+                close();
+                throw x;
+            }
+            catch (RuntimeException x)
+            {
+                LOG.warn(this + ": unexpected exception", x);
+                close();
+                throw x;
+            }
+            finally
+            {
+                LOG.debug("{}: end reading from server", this);
+            }
+        }
+
+        public void onInputShutdown() throws IOException
+        {
+        }
+
+        private void writeData() throws IOException
+        {
+            // This method is called from handle() and closeServer()
+            // which may happen concurrently (e.g. a client closing
+            // while reading from the server), so needs synchronization
+            synchronized (this)
+            {
+                if (_data != null)
+                {
+                    try
+                    {
+                        int written = write(_endPoint, _data, _context);
+                        LOG.debug("{}: written to server {} bytes", this, written);
+                    }
+                    finally
+                    {
+                        // Attempt once to write the data; if the write fails (for example
+                        // because the connection is already closed), clear the data and
+                        // give up to avoid to continue to write data to a closed connection
+                        _data = null;
+                    }
+                }
+            }
+        }
+
+        public void setConnection(ClientToProxyConnection connection)
+        {
+            _toClient = connection;
+        }
+
+        public long getTimeStamp()
+        {
+            return _timestamp;
+        }
+
+        public void setTimeStamp(long timestamp)
+        {
+            _timestamp = timestamp;
+        }
+
+        public void setEndPoint(AsyncEndPoint endpoint)
+        {
+            _endPoint = endpoint;
+        }
+
+        public boolean isIdle()
+        {
+            return false;
+        }
+
+        public boolean isSuspended()
+        {
+            return false;
+        }
+
+        public void onClose()
+        {
+        }
+
+        public void ready()
+        {
+            _ready.countDown();
+        }
+
+        public void waitReady(long timeout) throws IOException
+        {
+            try
+            {
+                _ready.await(timeout, TimeUnit.MILLISECONDS);
+            }
+            catch (final InterruptedException x)
+            {
+                throw new IOException()
+                {{
+                        initCause(x);
+                    }};
+            }
+        }
+
+        public void closeClient() throws IOException
+        {
+            _toClient.closeClient();
+        }
+
+        public void closeServer() throws IOException
+        {
+            _endPoint.close();
+        }
+
+        public void close()
+        {
+            try
+            {
+                closeClient();
+            }
+            catch (IOException x)
+            {
+                LOG.debug(this + ": unexpected exception closing the client", x);
+            }
+
+            try
+            {
+                closeServer();
+            }
+            catch (IOException x)
+            {
+                LOG.debug(this + ": unexpected exception closing the server", x);
+            }
+        }
+
+        public void shutdownOutput() throws IOException
+        {
+            writeData();
+            _endPoint.shutdownOutput();
+        }
+
+        public void onIdleExpired(long idleForMs)
+        {
+            try
+            {
+                LOG.debug("{} idle expired", this);
+                if (_endPoint.isOutputShutdown())
+                    close();
+                else
+                    shutdownOutput();
+            }
+            catch(Exception e)
+            {
+                LOG.debug(e);
+                close();
+            }
+        }
+    }
+
+    public class ClientToProxyConnection implements AsyncConnection
+    {
+        private final Buffer _buffer = new IndirectNIOBuffer(4096);
+        private final ConcurrentMap<String, Object> _context;
+        private final SocketChannel _channel;
+        private final EndPoint _endPoint;
+        private final long _timestamp;
+        private volatile ProxyToServerConnection _toServer;
+        private boolean _firstTime = true;
+
+        public ClientToProxyConnection(ConcurrentMap<String, Object> context, SocketChannel channel, EndPoint endPoint, long timestamp)
+        {
+            _context = context;
+            _channel = channel;
+            _endPoint = endPoint;
+            _timestamp = timestamp;
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder builder = new StringBuilder("ClientToProxy");
+            builder.append("(:").append(_endPoint.getLocalPort());
+            builder.append("<=>:").append(_endPoint.getRemotePort());
+            return builder.append(")").toString();
+        }
+
+        public Connection handle() throws IOException
+        {
+            LOG.debug("{}: begin reading from client", this);
+            try
+            {
+                if (_firstTime)
+                {
+                    _firstTime = false;
+                    register(_channel, _toServer);
+                    LOG.debug("{}: registered channel {} with connection {}", this, _channel, _toServer);
+                }
+
+                while (true)
+                {
+                    int read = read(_endPoint, _buffer, _context);
+
+                    if (read == -1)
+                    {
+                        LOG.debug("{}: client closed connection {}", this, _endPoint);
+
+                        if (_endPoint.isOutputShutdown() || !_endPoint.isOpen())
+                            closeServer();
+                        else
+                            _toServer.shutdownOutput();
+
+                        break;
+                    }
+
+                    if (read == 0)
+                        break;
+
+                    LOG.debug("{}: read from client {} bytes {}", this, read, _endPoint);
+                    int written = write(_toServer._endPoint, _buffer, _context);
+                    LOG.debug("{}: written to {} {} bytes", this, _toServer, written);
+                }
+                return this;
+            }
+            catch (ClosedChannelException x)
+            {
+                LOG.debug(x);
+                closeServer();
+                throw x;
+            }
+            catch (IOException x)
+            {
+                LOG.warn(this + ": unexpected exception", x);
+                close();
+                throw x;
+            }
+            catch (RuntimeException x)
+            {
+                LOG.warn(this + ": unexpected exception", x);
+                close();
+                throw x;
+            }
+            finally
+            {
+                LOG.debug("{}: end reading from client", this);
+            }
+        }
+
+        public void onInputShutdown() throws IOException
+        {
+        }
+
+        public long getTimeStamp()
+        {
+            return _timestamp;
+        }
+
+        public boolean isIdle()
+        {
+            return false;
+        }
+
+        public boolean isSuspended()
+        {
+            return false;
+        }
+
+        public void onClose()
+        {
+        }
+
+        public void setConnection(ProxyToServerConnection connection)
+        {
+            _toServer = connection;
+        }
+
+        public void closeClient() throws IOException
+        {
+            _endPoint.close();
+        }
+
+        public void closeServer() throws IOException
+        {
+            _toServer.closeServer();
+        }
+
+        public void close()
+        {
+            try
+            {
+                closeClient();
+            }
+            catch (IOException x)
+            {
+                LOG.debug(this + ": unexpected exception closing the client", x);
+            }
+
+            try
+            {
+                closeServer();
+            }
+            catch (IOException x)
+            {
+                LOG.debug(this + ": unexpected exception closing the server", x);
+            }
+        }
+
+        public void shutdownOutput() throws IOException
+        {
+            _endPoint.shutdownOutput();
+        }
+
+        public void onIdleExpired(long idleForMs)
+        {
+            try
+            {
+                LOG.debug("{} idle expired", this);
+                if (_endPoint.isOutputShutdown())
+                    close();
+                else
+                    shutdownOutput();
+            }
+            catch(Exception e)
+            {
+                LOG.debug(e);
+                close();
+            }
+        }
+    }
+
+    /**
+     * Add a whitelist entry to an existing handler configuration
+     *
+     * @param entry new whitelist entry
+     */
+    public void addWhite(String entry)
+    {
+        add(entry, _white);
+    }
+
+    /**
+     * Add a blacklist entry to an existing handler configuration
+     *
+     * @param entry new blacklist entry
+     */
+    public void addBlack(String entry)
+    {
+        add(entry, _black);
+    }
+
+    /**
+     * Re-initialize the whitelist of existing handler object
+     *
+     * @param entries array of whitelist entries
+     */
+    public void setWhite(String[] entries)
+    {
+        set(entries, _white);
+    }
+
+    /**
+     * Re-initialize the blacklist of existing handler object
+     *
+     * @param entries array of blacklist entries
+     */
+    public void setBlack(String[] entries)
+    {
+        set(entries, _black);
+    }
+
+    /**
+     * Helper method to process a list of new entries and replace
+     * the content of the specified host map
+     *
+     * @param entries new entries
+     * @param hostMap target host map
+     */
+    protected void set(String[] entries, HostMap<String> hostMap)
+    {
+        hostMap.clear();
+
+        if (entries != null && entries.length > 0)
+        {
+            for (String addrPath : entries)
+            {
+                add(addrPath, hostMap);
+            }
+        }
+    }
+
+    /**
+     * Helper method to process the new entry and add it to
+     * the specified host map.
+     *
+     * @param entry      new entry
+     * @param hostMap target host map
+     */
+    private void add(String entry, HostMap<String> hostMap)
+    {
+        if (entry != null && entry.length() > 0)
+        {
+            entry = entry.trim();
+            if (hostMap.get(entry) == null)
+            {
+                hostMap.put(entry, entry);
+            }
+        }
+    }
+
+    /**
+     * Check the request hostname against white- and blacklist.
+     *
+     * @param host hostname to check
+     * @return true if hostname is allowed to be proxied
+     */
+    public boolean validateDestination(String host)
+    {
+        if (_white.size() > 0)
+        {
+            Object whiteObj = _white.getLazyMatches(host);
+            if (whiteObj == null)
+            {
+                return false;
+            }
+        }
+
+        if (_black.size() > 0)
+        {
+            Object blackObj = _black.getLazyMatches(host);
+            if (blackObj != null)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpThis(out);
+        if (_privateThreadPool)
+            dump(out, indent, Arrays.asList(_threadPool, _selectorManager), TypeUtil.asList(getHandlers()), getBeans());
+        else
+            dump(out, indent, Arrays.asList(_selectorManager), TypeUtil.asList(getHandlers()), getBeans());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ContextHandler.java b/src/java/org/eclipse/jetty/server/handler/ContextHandler.java
new file mode 100644
index 0000000..16dec37
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -0,0 +1,2569 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextAttributeEvent;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequestAttributeListener;
+import javax.servlet.ServletRequestEvent;
+import javax.servlet.ServletRequestListener;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.FilterRegistration.Dynamic;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/**
+ * ContextHandler.
+ *
+ * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
+ *
+ * <p>
+ * If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
+ * context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
+ * <p>
+ * The maximum size of a form that can be processed by this context is controlled by the system properties org.eclipse.jetty.server.Request.maxFormKeys
+ * and org.eclipse.jetty.server.Request.maxFormContentSize.  These can also be configured with {@link #setMaxFormContentSize(int)} and {@link #setMaxFormKeys(int)}
+ * 
+ * @org.apache.xbean.XBean description="Creates a basic HTTP context"
+ */
+public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
+{
+    private static final Logger LOG = Log.getLogger(ContextHandler.class);
+
+    private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
+
+    /**
+     * If a context attribute with this name is set, it is interpreted as a comma separated list of attribute name. Any other context attributes that are set
+     * with a name from this list will result in a call to {@link #setManagedAttribute(String, Object)}, which typically initiates the creation of a JMX MBean
+     * for the attribute value.
+     */
+    public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the current ServletContext implementation.
+     *
+     * @return ServletContext implementation
+     */
+    public static Context getCurrentContext()
+    {
+        return __context.get();
+    }
+
+    protected Context _scontext;
+
+    private final AttributesMap _attributes;
+    private final AttributesMap _contextAttributes;
+    private final Map<String, String> _initParams;
+    private ClassLoader _classLoader;
+    private String _contextPath = "/";
+    private String _displayName;
+    private Resource _baseResource;
+    private MimeTypes _mimeTypes;
+    private Map<String, String> _localeEncodingMap;
+    private String[] _welcomeFiles;
+    private ErrorHandler _errorHandler;
+    private String[] _vhosts;
+    private Set<String> _connectors;
+    private EventListener[] _eventListeners;
+    private Logger _logger;
+    private boolean _allowNullPathInfo;
+    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
+    private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
+    private boolean _compactPath = false;
+    private boolean _aliasesAllowed = false;
+
+    private Object _contextListeners;
+    private Object _contextAttributeListeners;
+    private Object _requestListeners;
+    private Object _requestAttributeListeners;
+    private Object _durableListeners;
+    private Map<String, Object> _managedAttributes;
+    private String[] _protectedTargets;
+    private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
+
+    private boolean _shutdown = false;
+    private boolean _available = true;
+    private volatile int _availability; // 0=STOPPED, 1=AVAILABLE, 2=SHUTDOWN, 3=UNAVAILABLE
+
+    private final static int __STOPPED = 0, __AVAILABLE = 1, __SHUTDOWN = 2, __UNAVAILABLE = 3;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler()
+    {
+        super();
+        _scontext = new Context();
+        _attributes = new AttributesMap();
+        _contextAttributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    protected ContextHandler(Context context)
+    {
+        super();
+        _scontext = context;
+        _attributes = new AttributesMap();
+        _contextAttributes = new AttributesMap();
+        _initParams = new HashMap<String, String>();
+        addAliasCheck(new ApproveNonExistentDirectoryAliases());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public ContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this();
+        setContextPath(contextPath);
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        dumpThis(out);
+        dump(out,indent,Collections.singletonList(new CLDump(getClassLoader())),TypeUtil.asList(getHandlers()),getBeans(),_initParams.entrySet(),
+                _attributes.getAttributeEntrySet(),_contextAttributes.getAttributeEntrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    public Context getServletContext()
+    {
+        return _scontext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the allowNullPathInfo true if /context is not redirected to /context/
+     */
+    public boolean getAllowNullPathInfo()
+    {
+        return _allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param allowNullPathInfo
+     *            true if /context is not redirected to /context/
+     */
+    public void setAllowNullPathInfo(boolean allowNullPathInfo)
+    {
+        _allowNullPathInfo = allowNullPathInfo;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (_errorHandler != null)
+        {
+            Server old_server = getServer();
+            if (old_server != null && old_server != server)
+                old_server.getContainer().update(this,_errorHandler,null,"error",true);
+            super.setServer(server);
+            if (server != null && server != old_server)
+                server.getContainer().update(this,null,_errorHandler,"error",true);
+            _errorHandler.setServer(server);
+        }
+        else
+            super.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @param vhosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public void setVirtualHosts(String[] vhosts)
+    {
+        if (vhosts == null)
+        {
+            _vhosts = vhosts;
+        }
+        else
+        {
+            _vhosts = new String[vhosts.length];
+            for (int i = 0; i < vhosts.length; i++)
+                _vhosts[i] = normalizeHostname(vhosts[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Either set virtual hosts or add to an existing set of virtual hosts.
+     *
+     * @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public void addVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)  // since this is add, we don't null the old ones
+        {
+            return;
+        }
+        else
+        {
+            List<String> currentVirtualHosts = null;
+            if (_vhosts != null)
+            {
+                currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+            }
+            else
+            {
+                currentVirtualHosts = new ArrayList<String>();
+            }
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String normVhost = normalizeHostname(virtualHosts[i]);
+                if (!currentVirtualHosts.contains(normVhost))
+                {
+                    currentVirtualHosts.add(normVhost);
+                }
+            }
+            _vhosts = currentVirtualHosts.toArray(new String[0]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
+     *
+     *  @param virtualHosts
+     *            Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
+     *            String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public void removeVirtualHosts(String[] virtualHosts)
+    {
+        if (virtualHosts == null)
+        {
+            return; // do nothing
+        }
+        else if ( _vhosts == null || _vhosts.length == 0)
+        {
+            return; // do nothing
+        }
+        else
+        {
+            List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
+
+            for (int i = 0; i < virtualHosts.length; i++)
+            {
+                String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
+                if (existingVirtualHosts.contains(toRemoveVirtualHost))
+                {
+                    existingVirtualHosts.remove(toRemoveVirtualHost);
+                }
+            }
+
+            if (existingVirtualHosts.isEmpty())
+            {
+                _vhosts = null; // if we ended up removing them all, just null out _vhosts
+            }
+            else
+            {
+                _vhosts = existingVirtualHosts.toArray(new String[0]);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
+     * virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
+     * matching virtual host name.
+     *
+     * @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
+     *         representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
+     */
+    public String[] getVirtualHosts()
+    {
+        return _vhosts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return an array of connector names that this context will accept a request from.
+     */
+    public String[] getConnectorNames()
+    {
+        if (_connectors == null || _connectors.size() == 0)
+            return null;
+
+        return _connectors.toArray(new String[_connectors.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the names of accepted connectors.
+     *
+     * Names are either "host:port" or a specific configured name for a connector.
+     *
+     * @param connectors
+     *            If non null, an array of connector names that this context will accept a request from.
+     */
+    public void setConnectorNames(String[] connectors)
+    {
+        if (connectors == null || connectors.length == 0)
+            _connectors = null;
+        else
+            _connectors = new HashSet<String>(Arrays.asList(connectors));
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _attributes.getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getAttributeNames()
+     */
+    @SuppressWarnings("unchecked")
+    public Enumeration getAttributeNames()
+    {
+        return AttributesMap.getAttributeNamesCopy(_attributes);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the attributes.
+     */
+    public Attributes getAttributes()
+    {
+        return _attributes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the classLoader.
+     */
+    public ClassLoader getClassLoader()
+    {
+        return _classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Make best effort to extract a file classpath from the context classloader
+     *
+     * @return Returns the classLoader.
+     */
+    public String getClassPath()
+    {
+        if (_classLoader == null || !(_classLoader instanceof URLClassLoader))
+            return null;
+        URLClassLoader loader = (URLClassLoader)_classLoader;
+        URL[] urls = loader.getURLs();
+        StringBuilder classpath = new StringBuilder();
+        for (int i = 0; i < urls.length; i++)
+        {
+            try
+            {
+                Resource resource = newResource(urls[i]);
+                File file = resource.getFile();
+                if (file != null && file.exists())
+                {
+                    if (classpath.length() > 0)
+                        classpath.append(File.pathSeparatorChar);
+                    classpath.append(file.getAbsolutePath());
+                }
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+            }
+        }
+        if (classpath.length() == 0)
+            return null;
+        return classpath.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the _contextPath.
+     */
+    public String getContextPath()
+    {
+        return _contextPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+     */
+    public String getInitParameter(String name)
+    {
+        return _initParams.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public String setInitParameter(String name, String value)
+    {
+        return _initParams.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getInitParameterNames()
+     */
+    @SuppressWarnings("rawtypes")
+    public Enumeration getInitParameterNames()
+    {
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the initParams.
+     */
+    public Map<String, String> getInitParams()
+    {
+        return _initParams;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#getServletContextName()
+     */
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public EventListener[] getEventListeners()
+    {
+        return _eventListeners;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the context event listeners.
+     *
+     * @param eventListeners
+     *            the event listeners
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void setEventListeners(EventListener[] eventListeners)
+    {
+        _contextListeners = null;
+        _contextAttributeListeners = null;
+        _requestListeners = null;
+        _requestAttributeListeners = null;
+
+        _eventListeners = eventListeners;
+
+        for (int i = 0; eventListeners != null && i < eventListeners.length; i++)
+        {
+            EventListener listener = _eventListeners[i];
+
+            if (listener instanceof ServletContextListener)
+                _contextListeners = LazyList.add(_contextListeners,listener);
+
+            if (listener instanceof ServletContextAttributeListener)
+                _contextAttributeListeners = LazyList.add(_contextAttributeListeners,listener);
+
+            if (listener instanceof ServletRequestListener)
+                _requestListeners = LazyList.add(_requestListeners,listener);
+
+            if (listener instanceof ServletRequestAttributeListener)
+                _requestAttributeListeners = LazyList.add(_requestAttributeListeners,listener);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a context event listeners.
+     *
+     * @see ServletContextListener
+     * @see ServletContextAttributeListener
+     * @see ServletRequestListener
+     * @see ServletRequestAttributeListener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        //Only listeners added before the context starts last through a stop/start cycle
+        if (!(isStarted() || isStarting()))
+            _durableListeners = LazyList.add(_durableListeners, listener);
+        
+        setEventListeners((EventListener[])LazyList.addToArray(getEventListeners(),listener,EventListener.class));
+    }
+    
+   
+    /**
+     * Apply any necessary restrictions on a programmatically added
+     * listener.
+     * 
+     * Superclasses should implement.
+     * 
+     * @param listener
+     */
+    public void restrictEventListener (EventListener listener)
+    {
+    }
+
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if this context is accepting new requests
+     */
+    public boolean isShutdown()
+    {
+        synchronized (this)
+        {
+            return !_shutdown;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
+     * requests can complete, but no new requests are accepted.
+     *
+     * @param shutdown
+     *            true if this context is (not?) accepting new requests
+     */
+    public void setShutdown(boolean shutdown)
+    {
+        synchronized (this)
+        {
+            _shutdown = shutdown;
+            _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return false if this context is unavailable (sends 503)
+     */
+    public boolean isAvailable()
+    {
+        synchronized (this)
+        {
+            return _available;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set Available status.
+     */
+    public void setAvailable(boolean available)
+    {
+        synchronized (this)
+        {
+            _available = available;
+            _availability = isRunning()?(_shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE):__STOPPED;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public Logger getLogger()
+    {
+        return _logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLogger(Logger logger)
+    {
+        _logger = logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _availability = __STOPPED;
+
+        if (_contextPath == null)
+            throw new IllegalStateException("Null contextPath");
+
+        _logger = Log.getLogger(getDisplayName() == null?getContextPath():getDisplayName());
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            if (_mimeTypes == null)
+                _mimeTypes = new MimeTypes();
+
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // defers the calling of super.doStart()
+            startContext();
+
+            synchronized(this)
+            {
+                _availability = _shutdown?__SHUTDOWN:_available?__AVAILABLE:__UNAVAILABLE;
+            }
+        }
+        finally
+        {
+            __context.set(old_context);
+
+            // reset the classloader
+            if (_classLoader != null)
+            {
+                current_thread.setContextClassLoader(old_classloader);
+            }
+
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
+     * insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
+     *
+     * @see org.eclipse.jetty.server.handler.ContextHandler.Context
+     */
+    protected void startContext() throws Exception
+    {
+        String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
+        if (managedAttributes != null)
+        {
+            _managedAttributes = new HashMap<String, Object>();
+            String[] attributes = managedAttributes.split(",");
+            for (String attribute : attributes)
+                _managedAttributes.put(attribute,null);
+
+            Enumeration e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = (String)e.nextElement();
+                Object value = _scontext.getAttribute(name);
+                checkManagedAttribute(name,value);
+            }
+        }
+
+        super.doStart();
+
+        if (_errorHandler != null)
+            _errorHandler.start();
+
+        // Context listeners
+        if (_contextListeners != null)
+        {
+            ServletContextEvent event = new ServletContextEvent(_scontext);
+            for (int i = 0; i < LazyList.size(_contextListeners); i++)
+            {
+                callContextInitialized(((ServletContextListener)LazyList.get(_contextListeners, i)), event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void callContextInitialized (ServletContextListener l, ServletContextEvent e)
+    {
+        l.contextInitialized(e);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void callContextDestroyed (ServletContextListener l, ServletContextEvent e)
+    {
+        l.contextDestroyed(e);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _availability = __STOPPED;
+
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+
+        Context old_context = __context.get();
+        __context.set(_scontext);
+        try
+        {
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            super.doStop();
+
+            // Context listeners
+            if (_contextListeners != null)
+            {
+                ServletContextEvent event = new ServletContextEvent(_scontext);
+                for (int i = LazyList.size(_contextListeners); i-- > 0;)
+                {
+                    ((ServletContextListener)LazyList.get(_contextListeners,i)).contextDestroyed(event);
+                }
+            }
+            
+            //remove all non-durable listeners
+            setEventListeners((EventListener[])LazyList.toArray(_durableListeners, EventListener.class));
+            _durableListeners = null;
+
+            if (_errorHandler != null)
+                _errorHandler.stop();
+
+            Enumeration e = _scontext.getAttributeNames();
+            while (e.hasMoreElements())
+            {
+                String name = (String)e.nextElement();
+                checkManagedAttribute(name,null);
+            }
+        }
+        finally
+        {
+            LOG.info("stopped {}",this);
+            __context.set(old_context);
+            // reset the classloader
+            if (_classLoader != null)
+                current_thread.setContextClassLoader(old_classloader);
+        }
+
+        _contextAttributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public boolean checkContext(final String target, final Request baseRequest, final HttpServletResponse response) throws IOException, ServletException
+    {
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        switch (_availability)
+        {
+            case __STOPPED:
+            case __SHUTDOWN:
+                return false;
+            case __UNAVAILABLE:
+                baseRequest.setHandled(true);
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return false;
+            default:
+                if ((DispatcherType.REQUEST.equals(dispatch) && baseRequest.isHandled()))
+                    return false;
+        }
+
+        // Check the vhosts
+        if (_vhosts != null && _vhosts.length > 0)
+        {
+            String vhost = normalizeHostname(baseRequest.getServerName());
+
+            boolean match = false;
+
+            // TODO non-linear lookup
+            for (int i = 0; !match && i < _vhosts.length; i++)
+            {
+                String contextVhost = _vhosts[i];
+                if (contextVhost == null)
+                    continue;
+                if (contextVhost.startsWith("*."))
+                {
+                    // wildcard only at the beginning, and only for one additional subdomain level
+                    match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
+                }
+                else
+                    match = contextVhost.equalsIgnoreCase(vhost);
+            }
+            if (!match)
+                return false;
+        }
+
+        // Check the connector
+        if (_connectors != null && _connectors.size() > 0)
+        {
+            String connector = AbstractHttpConnection.getCurrentConnection().getConnector().getName();
+            if (connector == null || !_connectors.contains(connector))
+                return false;
+        }
+
+        // Are we not the root context?
+        if (_contextPath.length() > 1)
+        {
+            // reject requests that are not for us
+            if (!target.startsWith(_contextPath))
+                return false;
+            if (target.length() > _contextPath.length() && target.charAt(_contextPath.length()) != '/')
+                return false;
+
+            // redirect null path infos
+            if (!_allowNullPathInfo && _contextPath.length() == target.length())
+            {
+                // context request must end with /
+                baseRequest.setHandled(true);
+                if (baseRequest.getQueryString() != null)
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH) + "?" + baseRequest.getQueryString());
+                else
+                    response.sendRedirect(URIUtil.addPaths(baseRequest.getRequestURI(),URIUtil.SLASH));
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doScope(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
+
+        Context old_context = null;
+        String old_context_path = null;
+        String old_servlet_path = null;
+        String old_path_info = null;
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        String pathInfo = target;
+
+        DispatcherType dispatch = baseRequest.getDispatcherType();
+
+        old_context = baseRequest.getContext();
+
+        // Are we already in this context?
+        if (old_context != _scontext)
+        {
+            // check the target.
+            if (DispatcherType.REQUEST.equals(dispatch) || DispatcherType.ASYNC.equals(dispatch) || (DispatcherType.ERROR.equals(dispatch) && baseRequest.getAsyncContinuation().isExpired()))
+            {
+                if (_compactPath)
+                    target = URIUtil.compactPath(target);
+                if (!checkContext(target,baseRequest,response))
+                    return;
+
+                if (target.length() > _contextPath.length())
+                {
+                    if (_contextPath.length() > 1)
+                        target = target.substring(_contextPath.length());
+                    pathInfo = target;
+                }
+                else if (_contextPath.length() == 1)
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = URIUtil.SLASH;
+                }
+                else
+                {
+                    target = URIUtil.SLASH;
+                    pathInfo = null;
+                }
+            }
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+        }
+
+        try
+        {
+            old_context_path = baseRequest.getContextPath();
+            old_servlet_path = baseRequest.getServletPath();
+            old_path_info = baseRequest.getPathInfo();
+
+            // Update the paths
+            baseRequest.setContext(_scontext);
+            __context.set(_scontext);
+            if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
+            {
+                if (_contextPath.length() == 1)
+                    baseRequest.setContextPath("");
+                else
+                    baseRequest.setContextPath(_contextPath);
+                baseRequest.setServletPath(null);
+                baseRequest.setPathInfo(pathInfo);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_context != _scontext)
+            {
+                // reset the classloader
+                if (_classLoader != null)
+                {
+                    current_thread.setContextClassLoader(old_classloader);
+                }
+
+                // reset the context and servlet path.
+                baseRequest.setContext(old_context);
+                __context.set(old_context);
+                baseRequest.setContextPath(old_context_path);
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ScopedHandler#doHandle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        final DispatcherType dispatch = baseRequest.getDispatcherType();
+        final boolean new_context = baseRequest.takeNewContext();
+        try
+        {
+            if (new_context)
+            {
+                // Handle the REALLY SILLY request events!
+                if (_requestAttributeListeners != null)
+                {
+                    final int s = LazyList.size(_requestAttributeListeners);
+                    for (int i = 0; i < s; i++)
+                        baseRequest.addEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
+                }
+
+                if (_requestListeners != null)
+                {
+                    final int s = LazyList.size(_requestListeners);
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (int i = 0; i < s; i++)
+                        ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestInitialized(sre);
+                }
+            }
+
+            if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target))
+                throw new HttpException(HttpServletResponse.SC_NOT_FOUND);
+
+            // start manual inline of nextHandle(target,baseRequest,request,response);
+            // noinspection ConstantIfStatement
+            if (never())
+                nextHandle(target,baseRequest,request,response);
+            else if (_nextScope != null && _nextScope == _handler)
+                _nextScope.doHandle(target,baseRequest,request,response);
+            else if (_handler != null)
+                _handler.handle(target,baseRequest,request,response);
+            // end manual inline
+        }
+        catch (HttpException e)
+        {
+            LOG.debug(e);
+            baseRequest.setHandled(true);
+            response.sendError(e.getStatus(),e.getReason());
+        }
+        finally
+        {
+            // Handle more REALLY SILLY request events!
+            if (new_context)
+            {
+                if (_requestListeners != null)
+                {
+                    final ServletRequestEvent sre = new ServletRequestEvent(_scontext,request);
+                    for (int i = LazyList.size(_requestListeners); i-- > 0;)
+                        ((ServletRequestListener)LazyList.get(_requestListeners,i)).requestDestroyed(sre);
+                }
+
+                if (_requestAttributeListeners != null)
+                {
+                    for (int i = LazyList.size(_requestAttributeListeners); i-- > 0;)
+                        baseRequest.removeEventListener(((EventListener)LazyList.get(_requestAttributeListeners,i)));
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Handle a runnable in this context
+     */
+    public void handle(Runnable runnable)
+    {
+        ClassLoader old_classloader = null;
+        Thread current_thread = null;
+        Context old_context = null;
+        try
+        {
+            old_context = __context.get();
+            __context.set(_scontext);
+
+            // Set the classloader
+            if (_classLoader != null)
+            {
+                current_thread = Thread.currentThread();
+                old_classloader = current_thread.getContextClassLoader();
+                current_thread.setContextClassLoader(_classLoader);
+            }
+
+            runnable.run();
+        }
+        finally
+        {
+            __context.set(old_context);
+            if (old_classloader != null)
+            {
+                current_thread.setContextClassLoader(old_classloader);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check the target. Called by {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} when a target within a context is determined. If
+     * the target is protected, 404 is returned. 
+     */
+    /* ------------------------------------------------------------ */
+    public boolean isProtectedTarget(String target)
+    {
+        if (target == null || _protectedTargets == null)
+            return false;
+        
+        while (target.startsWith("//"))
+            target=URIUtil.compactPath(target);
+        
+        boolean isProtected = false;
+        int i=0;
+        while (!isProtected && i<_protectedTargets.length)
+        {
+            isProtected = StringUtil.startsWithIgnoreCase(target, _protectedTargets[i++]);
+        }
+        return isProtected;
+    }
+    
+    
+    public void setProtectedTargets (String[] targets)
+    {
+        if (targets == null)
+        {
+            _protectedTargets = null;
+            return;
+        }
+        
+        _protectedTargets = new String[targets.length];
+        System.arraycopy(targets, 0, _protectedTargets, 0, targets.length);
+    }
+    
+    public String[] getProtectedTargets ()
+    {
+        if (_protectedTargets == null)
+            return null;
+        
+        String[] tmp = new String[_protectedTargets.length];
+        System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length);
+        return tmp;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        checkManagedAttribute(name,null);
+        _attributes.removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
+     * a context. No attribute listener events are triggered by this API.
+     *
+     * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object value)
+    {
+        checkManagedAttribute(name,value);
+        _attributes.setAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param attributes
+     *            The attributes to set.
+     */
+    public void setAttributes(Attributes attributes)
+    {
+        _attributes.clearAttributes();
+        _attributes.addAll(attributes);
+        Enumeration e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = (String)e.nextElement();
+            checkManagedAttribute(name,attributes.getAttribute(name));
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearAttributes()
+    {
+        Enumeration e = _attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name = (String)e.nextElement();
+            checkManagedAttribute(name,null);
+        }
+        _attributes.clearAttributes();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void checkManagedAttribute(String name, Object value)
+    {
+        if (_managedAttributes != null && _managedAttributes.containsKey(name))
+        {
+            setManagedAttribute(name,value);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setManagedAttribute(String name, Object value)
+    {
+        Object old = _managedAttributes.put(name,value);
+        getServer().getContainer().update(this,old,value,name,true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param classLoader
+     *            The classLoader to set.
+     */
+    public void setClassLoader(ClassLoader classLoader)
+    {
+        _classLoader = classLoader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextPath
+     *            The _contextPath to set.
+     */
+    public void setContextPath(String contextPath)
+    {
+        if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
+            throw new IllegalArgumentException("ends with /");
+        _contextPath = contextPath;
+
+        if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
+        {
+            Handler[] contextCollections = getServer().getChildHandlersByClass(ContextHandlerCollection.class);
+            for (int h = 0; contextCollections != null && h < contextCollections.length; h++)
+                ((ContextHandlerCollection)contextCollections[h]).mapContexts();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletContextName
+     *            The servletContextName to set.
+     */
+    public void setDisplayName(String servletContextName)
+    {
+        _displayName = servletContextName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    public String getResourceBase()
+    {
+        if (_baseResource == null)
+            return null;
+        return _baseResource.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param base
+     *            The resourceBase to set.
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource = base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resourceBase
+     *            The base resource as a string.
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if aliases are allowed
+     */
+    public boolean isAliases()
+    {
+        return _aliasesAllowed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param aliases
+     *            aliases are allowed
+     */
+    public void setAliases(boolean aliases)
+    {
+        _aliasesAllowed = aliases;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the mimeTypes.
+     */
+    public MimeTypes getMimeTypes()
+    {
+        if (_mimeTypes == null)
+            _mimeTypes = new MimeTypes();
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param mimeTypes
+     *            The mimeTypes to set.
+     */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void setWelcomeFiles(String[] files)
+    {
+        _welcomeFiles = files;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The names of the files which the server should consider to be welcome files in this context.
+     * @see <a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html">The Servlet Specification</a>
+     * @see #setWelcomeFiles
+     */
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorHandler.
+     */
+    public ErrorHandler getErrorHandler()
+    {
+        return _errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorHandler
+     *            The errorHandler to set.
+     */
+    public void setErrorHandler(ErrorHandler errorHandler)
+    {
+        if (errorHandler != null)
+            errorHandler.setServer(getServer());
+        if (getServer() != null)
+            getServer().getContainer().update(this,_errorHandler,errorHandler,"errorHandler",true);
+        _errorHandler = errorHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxFormContentSize()
+    {
+        return _maxFormContentSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum size of a form post, to protect against DOS attacks from large forms.
+     * @param maxSize
+     */
+    public void setMaxFormContentSize(int maxSize)
+    {
+        _maxFormContentSize = maxSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getMaxFormKeys()
+    {
+        return _maxFormKeys;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
+     * @param max
+     */
+    public void setMaxFormKeys(int max)
+    {
+        _maxFormKeys = max;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public boolean isCompactPath()
+    {
+        return _compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param compactPath
+     *            True if URLs are compacted to replace multiple '/'s with a single '/'
+     */
+    public void setCompactPath(boolean compactPath)
+    {
+        _compactPath = compactPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        String[] vhosts = getVirtualHosts();
+
+        StringBuilder b = new StringBuilder();
+
+        Package pkg = getClass().getPackage();
+        if (pkg != null)
+        {
+            String p = pkg.getName();
+            if (p != null && p.length() > 0)
+            {
+                String[] ss = p.split("\\.");
+                for (String s : ss)
+                    b.append(s.charAt(0)).append('.');
+            }
+        }
+        b.append(getClass().getSimpleName());
+        b.append('{').append(getContextPath()).append(',').append(getBaseResource());
+
+        if (vhosts != null && vhosts.length > 0)
+            b.append(',').append(vhosts[0]);
+        b.append('}');
+
+        return b.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException
+    {
+        if (className == null)
+            return null;
+
+        if (_classLoader == null)
+            return Loader.loadClass(this.getClass(),className);
+
+        return _classLoader.loadClass(className);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addLocaleEncoding(String locale, String encoding)
+    {
+        if (_localeEncodingMap == null)
+            _localeEncodingMap = new HashMap<String, String>();
+        _localeEncodingMap.put(locale,encoding);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getLocaleEncoding(String locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale);
+        return encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
+     * language is looked up.
+     *
+     * @param locale
+     *            a <code>Locale</code> value
+     * @return a <code>String</code> representing the character encoding for the locale or null if none found.
+     */
+    public String getLocaleEncoding(Locale locale)
+    {
+        if (_localeEncodingMap == null)
+            return null;
+        String encoding = _localeEncodingMap.get(locale.toString());
+        if (encoding == null)
+            encoding = _localeEncodingMap.get(locale.getLanguage());
+        return encoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path == null || !path.startsWith(URIUtil.SLASH))
+            throw new MalformedURLException(path);
+
+        if (_baseResource == null)
+            return null;
+
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = _baseResource.addPath(path);
+            
+            if (checkAlias(path,resource))
+                return resource;
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean checkAlias(String path, Resource resource)
+    {
+        // Is the resource aliased?
+        if (!_aliasesAllowed && resource.getAlias() != null)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());
+
+            // alias checks
+            for (Iterator<AliasCheck> i=_aliasChecks.iterator();i.hasNext();)
+            {
+                AliasCheck check = i.next();
+                if (check.check(path,resource))
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Aliased resource: " + resource + " approved by " + check);
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert URL to Resource wrapper for {@link Resource#newResource(URL)} enables extensions to provide alternate resource implementations.
+     */
+    public Resource newResource(URL url) throws IOException
+    {
+        return Resource.newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
+     *
+     * @param urlOrPath
+     *            The URL or path to convert
+     * @return The Resource for the URL/path
+     * @throws IOException
+     *             The Resource could not be created.
+     */
+    public Resource newResource(String urlOrPath) throws IOException
+    {
+        return Resource.newResource(urlOrPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Set<String> getResourcePaths(String path)
+    {
+        try
+        {
+            path = URIUtil.canonicalPath(path);
+            Resource resource = getResource(path);
+
+            if (resource != null && resource.exists())
+            {
+                if (!path.endsWith(URIUtil.SLASH))
+                    path = path + URIUtil.SLASH;
+
+                String[] l = resource.list();
+                if (l != null)
+                {
+                    HashSet<String> set = new HashSet<String>();
+                    for (int i = 0; i < l.length; i++)
+                        set.add(path + l[i]);
+                    return set;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    private String normalizeHostname(String host)
+    {
+        if (host == null)
+            return null;
+
+        if (host.endsWith("."))
+            return host.substring(0,host.length() - 1);
+
+        return host;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an AliasCheck instance to possibly permit aliased resources
+     * @param check The alias checker
+     */
+    public void addAliasCheck(AliasCheck check)
+    {
+        _aliasChecks.add(check);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Mutable list of Alias checks
+     */
+    public List<AliasCheck> getAliasChecks()
+    {
+        return _aliasChecks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Context.
+     * <p>
+     * A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
+     * </p>
+     *
+     *
+     */
+    public class Context implements ServletContext
+    {
+        protected int _majorVersion = 3;
+        protected int _minorVersion = 0;
+        protected boolean _enabled = true; //whether or not the dynamic API is enabled for callers
+
+        /* ------------------------------------------------------------ */
+        protected Context()
+        {
+        }
+
+        /* ------------------------------------------------------------ */
+        public ContextHandler getContextHandler()
+        {
+            // TODO reduce visibility of this method
+            return ContextHandler.this;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getContext(java.lang.String)
+         */
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            List<ContextHandler> contexts = new ArrayList<ContextHandler>();
+            Handler[] handlers = getServer().getChildHandlersByClass(ContextHandler.class);
+            String matched_path = null;
+
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    // look first for vhost matching context only
+                    if (getVirtualHosts() != null && getVirtualHosts().length > 0)
+                    {
+                        if (ch.getVirtualHosts() != null && ch.getVirtualHosts().length > 0)
+                        {
+                            for (String h1 : getVirtualHosts())
+                                for (String h2 : ch.getVirtualHosts())
+                                    if (h1.equals(h2))
+                                    {
+                                        if (matched_path == null || context_path.length() > matched_path.length())
+                                        {
+                                            contexts.clear();
+                                            matched_path = context_path;
+                                        }
+
+                                        if (matched_path.equals(context_path))
+                                            contexts.add(ch);
+                                    }
+                        }
+                    }
+                    else
+                    {
+                        if (matched_path == null || context_path.length() > matched_path.length())
+                        {
+                            contexts.clear();
+                            matched_path = context_path;
+                        }
+
+                        if (matched_path.equals(context_path))
+                            contexts.add(ch);
+                    }
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+
+            // try again ignoring virtual hosts
+            matched_path = null;
+            for (Handler handler : handlers)
+            {
+                if (handler == null)
+                    continue;
+                ContextHandler ch = (ContextHandler)handler;
+                String context_path = ch.getContextPath();
+
+                if (uripath.equals(context_path) || (uripath.startsWith(context_path) && uripath.charAt(context_path.length()) == '/')
+                        || "/".equals(context_path))
+                {
+                    if (matched_path == null || context_path.length() > matched_path.length())
+                    {
+                        contexts.clear();
+                        matched_path = context_path;
+                    }
+
+                    if (matched_path.equals(context_path))
+                        contexts.add(ch);
+                }
+            }
+
+            if (contexts.size() > 0)
+                return contexts.get(0)._scontext;
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getMajorVersion()
+         */
+        @Override
+        public int getMajorVersion()
+        {
+            return 3;
+        }
+      
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getMimeType(java.lang.String)
+         */
+        @Override
+        public String getMimeType(String file)
+        {
+            if (_mimeTypes == null)
+                return null;
+            Buffer mime = _mimeTypes.getMimeByExtension(file);
+            if (mime != null)
+                return mime.toString();
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getMinorVersion()
+         */
+        @Override
+        public int getMinorVersion()
+        {
+            return 0;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRequestDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getRequestDispatcher(String uriInContext)
+        {
+            if (uriInContext == null)
+                return null;
+
+            if (!uriInContext.startsWith("/"))
+                return null;
+
+            try
+            {
+                String query = null;
+                int q = 0;
+                if ((q = uriInContext.indexOf('?')) > 0)
+                {
+                    query = uriInContext.substring(q + 1);
+                    uriInContext = uriInContext.substring(0,q);
+                }
+
+                String pathInContext = URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
+                if (pathInContext!=null)
+                {
+                    String uri = URIUtil.addPaths(getContextPath(),uriInContext);
+                    ContextHandler context = ContextHandler.this;
+                    return new Dispatcher(context,uri,pathInContext,query);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getRealPath(java.lang.String)
+         */
+        @Override
+        public String getRealPath(String path)
+        {
+            if (path == null)
+                return null;
+            if (path.length() == 0)
+                path = URIUtil.SLASH;
+            else if (path.charAt(0) != '/')
+                path = URIUtil.SLASH + path;
+
+            try
+            {
+                Resource resource = ContextHandler.this.getResource(path);
+                if (resource != null)
+                {
+                    File file = resource.getFile();
+                    if (file != null)
+                        return file.getCanonicalPath();
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+            }
+
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            Resource resource = ContextHandler.this.getResource(path);
+            if (resource != null && resource.exists())
+                return resource.getURL();
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourceAsStream(java.lang.String)
+         */
+        @Override
+        public InputStream getResourceAsStream(String path)
+        {
+            try
+            {
+                URL url = getResource(path);
+                if (url == null)
+                    return null;
+                Resource r = Resource.newResource(url);
+                return r.getInputStream();
+            }
+            catch (Exception e)
+            {
+                LOG.ignore(e);
+                return null;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getResourcePaths(java.lang.String)
+         */
+        @Override
+        public Set getResourcePaths(String path)
+        {
+            return ContextHandler.this.getResourcePaths(path);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServerInfo()
+         */
+        @Override
+        public String getServerInfo()
+        {
+            return "jetty/" + Server.getVersion();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServlet(java.lang.String)
+         */
+        @Override
+        @Deprecated
+        public Servlet getServlet(String name) throws ServletException
+        {
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServletNames()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration getServletNames()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServlets()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        @Deprecated
+        public Enumeration getServlets()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.Exception, java.lang.String)
+         */
+        @Override
+        public void log(Exception exception, String msg)
+        {
+            _logger.warn(msg,exception);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String)
+         */
+        @Override
+        public void log(String msg)
+        {
+            _logger.info(msg);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#log(java.lang.String, java.lang.Throwable)
+         */
+        @Override
+        public void log(String message, Throwable throwable)
+        {
+            _logger.warn(message,throwable);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameter(java.lang.String)
+         */
+        @Override
+        public String getInitParameter(String name)
+        {
+            return ContextHandler.this.getInitParameter(name);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getInitParameterNames()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        public Enumeration getInitParameterNames()
+        {
+            return ContextHandler.this.getInitParameterNames();
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized Object getAttribute(String name)
+        {
+            Object o = ContextHandler.this.getAttribute(name);
+            if (o == null && _contextAttributes != null)
+                o = _contextAttributes.getAttribute(name);
+            return o;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getAttributeNames()
+         */
+        @SuppressWarnings("unchecked")
+        @Override
+        public synchronized Enumeration getAttributeNames()
+        {
+            HashSet<String> set = new HashSet<String>();
+            if (_contextAttributes != null)
+            {
+                Enumeration<String> e = _contextAttributes.getAttributeNames();
+                while (e.hasMoreElements())
+                    set.add(e.nextElement());
+            }
+            Enumeration<String> e = _attributes.getAttributeNames();
+            while (e.hasMoreElements())
+                set.add(e.nextElement());
+
+            return Collections.enumeration(set);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
+         */
+        @Override
+        public synchronized void setAttribute(String name, Object value)
+        {
+            checkManagedAttribute(name,value);
+            Object old_value = _contextAttributes.getAttribute(name);
+
+            if (value == null)
+                _contextAttributes.removeAttribute(name);
+            else
+                _contextAttributes.setAttribute(name,value);
+
+            if (_contextAttributeListeners != null)
+            {
+                ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value == null?value:old_value);
+
+                for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
+                {
+                    ServletContextAttributeListener l = (ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i);
+
+                    if (old_value == null)
+                        l.attributeAdded(event);
+                    else if (value == null)
+                        l.attributeRemoved(event);
+                    else
+                        l.attributeReplaced(event);
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#removeAttribute(java.lang.String)
+         */
+        @Override
+        public synchronized void removeAttribute(String name)
+        {
+            checkManagedAttribute(name,null);
+
+            if (_contextAttributes == null)
+            {
+                // Set it on the handler
+                _attributes.removeAttribute(name);
+                return;
+            }
+
+            Object old_value = _contextAttributes.getAttribute(name);
+            _contextAttributes.removeAttribute(name);
+            if (old_value != null)
+            {
+                if (_contextAttributeListeners != null)
+                {
+                    ServletContextAttributeEvent event = new ServletContextAttributeEvent(_scontext,name,old_value);
+
+                    for (int i = 0; i < LazyList.size(_contextAttributeListeners); i++)
+                        ((ServletContextAttributeListener)LazyList.get(_contextAttributeListeners,i)).attributeRemoved(event);
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * @see javax.servlet.ServletContext#getServletContextName()
+         */
+        @Override
+        public String getServletContextName()
+        {
+            String name = ContextHandler.this.getDisplayName();
+            if (name == null)
+                name = ContextHandler.this.getContextPath();
+            return name;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String getContextPath()
+        {
+            if ((_contextPath != null) && _contextPath.equals(URIUtil.SLASH))
+                return "";
+
+            return _contextPath;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return "ServletContext@" + ContextHandler.this.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            if (ContextHandler.this.getInitParameter(name) != null)
+                return false;
+            ContextHandler.this.getInitParams().put(name,value);
+            return true;
+        }
+
+        /* ------------------------------------------------------------ */
+        final private static String __unimplmented="Unimplemented - use org.eclipse.jetty.servlet.ServletContextHandler";
+
+        @Override
+        public Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, Filter filter)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Dynamic addFilter(String filterName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            LOG.warn(__unimplmented);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            try
+            {
+                Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):_classLoader.loadClass(className);
+                addListener(clazz);
+            }
+            catch (ClassNotFoundException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            ContextHandler.this.addEventListener(t);
+            ContextHandler.this.restrictEventListener(t);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            try
+            {
+                EventListener e = createListener(listenerClass);
+                ContextHandler.this.addEventListener(e);
+                ContextHandler.this.restrictEventListener(e);
+            }
+            catch (ServletException e)
+            {
+                throw new IllegalArgumentException(e);
+            }
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                return clazz.newInstance();
+            }
+            catch (InstantiationException e)
+            {
+                throw new ServletException(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        @Override
+        public ClassLoader getClassLoader()
+        {
+            AccessController.checkPermission(new RuntimePermission("getClassLoader"));
+            return _classLoader;
+        }
+
+        @Override
+        public int getEffectiveMajorVersion()
+        {
+            return _majorVersion;
+        }
+
+        @Override
+        public int getEffectiveMinorVersion()
+        {
+            return _minorVersion;
+        }
+
+        public void setEffectiveMajorVersion (int v)
+        {
+            _majorVersion = v;
+        }
+        
+        public void setEffectiveMinorVersion (int v)
+        {
+            _minorVersion = v;
+        }
+        
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            LOG.warn(__unimplmented);
+            return null;
+        }
+
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+            
+        }
+        
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException ();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            // TODO Auto-generated method stub
+            
+        }
+
+        public void setEnabled(boolean enabled)
+        {
+            _enabled = enabled;
+        }
+
+        public boolean isEnabled()
+        {
+            return _enabled;
+        }
+    }
+
+    private static class CLDump implements Dumpable
+    {
+        final ClassLoader _loader;
+
+        CLDump(ClassLoader loader)
+        {
+            _loader = loader;
+        }
+
+        public String dump()
+        {
+            return AggregateLifeCycle.dump(this);
+        }
+
+        public void dump(Appendable out, String indent) throws IOException
+        {
+            out.append(String.valueOf(_loader)).append("\n");
+
+            if (_loader != null)
+            {
+                Object parent = _loader.getParent();
+                if (parent != null)
+                {
+                    if (!(parent instanceof Dumpable))
+                        parent = new CLDump((ClassLoader)parent);
+
+                    if (_loader instanceof URLClassLoader)
+                        AggregateLifeCycle.dump(out,indent,TypeUtil.asList(((URLClassLoader)_loader).getURLs()),Collections.singleton(parent));
+                    else
+                        AggregateLifeCycle.dump(out,indent,Collections.singleton(parent));
+                }
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Interface to check aliases
+     */
+    public interface AliasCheck
+    {
+        /* ------------------------------------------------------------ */
+        /** Check an alias
+         * @param path The path the aliased resource was created for
+         * @param resource The aliased resourced
+         * @return True if the resource is OK to be served.
+         */
+        boolean check(String path, Resource resource);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with same suffix.
+     * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be
+     * approved because both the resource and alias end with ".html".
+     */
+    @Deprecated
+    public static class ApproveSameSuffixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApproveSameSuffixAlias is not safe for production");
+        }
+        
+        public boolean check(String path, Resource resource)
+        {
+            int dot = path.lastIndexOf('.');
+            if (dot<0)
+                return false;
+            String suffix=path.substring(dot);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases with a path prefix.
+     * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be
+     * approved because both the resource and alias end with "/foobar.html".
+     */
+    @Deprecated
+    public static class ApprovePathPrefixAliases implements AliasCheck
+    {
+        {
+            LOG.warn("ApprovePathPrefixAliases is not safe for production");
+        }
+        
+        public boolean check(String path, Resource resource)
+        {
+            int slash = path.lastIndexOf('/');
+            if (slash<0 || slash==path.length()-1)
+                return false;
+            String suffix=path.substring(slash);
+            return resource.toString().endsWith(suffix);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Approve Aliases of a non existent directory.
+     * If a directory "/foobar/" does not exist, then the resource is 
+     * aliased to "/foobar".  Accept such aliases.
+     */
+    public static class ApproveNonExistentDirectoryAliases implements AliasCheck
+    {
+        public boolean check(String path, Resource resource)
+        {
+            if (resource.exists())
+                return false;
+            
+            String a=resource.getAlias().toString();
+            String r=resource.getURL().toString();
+            
+            if (a.length()>r.length())
+                return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/");
+            else
+                return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/");
+        }
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java b/src/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
new file mode 100644
index 0000000..47d4c20
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java
@@ -0,0 +1,332 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.AsyncContinuation;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** ContextHandlerCollection.
+ * 
+ * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a 
+ * {@link org.eclipse.jetty.http.PathMap} to it's contained handlers based
+ * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
+ * The contexts do not need to be directly contained, only children of the contained handlers.
+ * Multiple contexts may have the same context path and they are called in order until one
+ * handles the request.  
+ * 
+ * @org.apache.xbean.XBean element="contexts"
+ */
+public class ContextHandlerCollection extends HandlerCollection
+{
+    private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
+ 
+    private volatile PathMap _contextMap;
+    private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
+    
+    /* ------------------------------------------------------------ */
+    public ContextHandlerCollection()
+    {
+        super(true);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remap the context paths.
+     */
+    public void mapContexts()
+    {
+        PathMap contextMap = new PathMap();
+        Handler[] branches = getHandlers();
+        
+        
+        for (int b=0;branches!=null && b<branches.length;b++)
+        {
+            Handler[] handlers=null;
+            
+            if (branches[b] instanceof ContextHandler)
+            {
+                handlers = new Handler[]{ branches[b] };
+            }
+            else if (branches[b] instanceof HandlerContainer)
+            {
+                handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
+            }
+            else 
+                continue;
+            
+            for (int i=0;i<handlers.length;i++)
+            {
+                ContextHandler handler=(ContextHandler)handlers[i];
+
+                String contextPath=handler.getContextPath();
+
+                if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
+                    throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
+
+                if(!contextPath.startsWith("/"))
+                    contextPath='/'+contextPath;
+
+                if (contextPath.length()>1)
+                {
+                    if (contextPath.endsWith("/"))
+                        contextPath+="*";
+                    else if (!contextPath.endsWith("/*"))
+                        contextPath+="/*";
+                }
+
+                Object contexts=contextMap.get(contextPath);
+                String[] vhosts=handler.getVirtualHosts();
+
+                
+                if (vhosts!=null && vhosts.length>0)
+                {
+                    Map hosts;
+
+                    if (contexts instanceof Map)
+                        hosts=(Map)contexts;
+                    else
+                    {
+                        hosts=new HashMap(); 
+                        hosts.put("*",contexts);
+                        contextMap.put(contextPath, hosts);
+                    }
+
+                    for (int j=0;j<vhosts.length;j++)
+                    {
+                        String vhost=vhosts[j];
+                        contexts=hosts.get(vhost);
+                        contexts=LazyList.add(contexts,branches[b]);
+                        hosts.put(vhost,contexts);
+                    }
+                }
+                else if (contexts instanceof Map)
+                {
+                    Map hosts=(Map)contexts;
+                    contexts=hosts.get("*");
+                    contexts= LazyList.add(contexts, branches[b]);
+                    hosts.put("*",contexts);
+                }
+                else
+                {
+                    contexts= LazyList.add(contexts, branches[b]);
+                    contextMap.put(contextPath, contexts);
+                }
+            }
+        }
+        _contextMap=contextMap;
+
+    }
+    
+
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
+     */
+    @Override
+    public void setHandlers(Handler[] handlers)
+    {
+        _contextMap=null;
+        super.setHandlers(handlers);
+        if (isStarted())
+            mapContexts();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        mapContexts();
+        super.doStart();
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+        if (handlers==null || handlers.length==0)
+	    return;
+	
+	AsyncContinuation async = baseRequest.getAsyncContinuation();
+	if (async.isAsync())
+	{
+	    ContextHandler context=async.getContextHandler();
+	    if (context!=null)
+	    {
+	        context.handle(target,baseRequest,request, response);
+	        return;
+	    }
+	}
+	
+	// data structure which maps a request to a context; first-best match wins
+	// { context path => 
+	//     { virtual host => context } 
+	// }
+	PathMap map = _contextMap;
+	if (map!=null && target!=null && target.startsWith("/"))
+	{
+	    // first, get all contexts matched by context path
+	    Object contexts = map.getLazyMatches(target);
+
+            for (int i=0; i<LazyList.size(contexts); i++)
+            {
+                // then, match against the virtualhost of each context
+                Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
+                Object list = entry.getValue();
+
+                if (list instanceof Map)
+                {
+                    Map hosts = (Map)list;
+                    String host = normalizeHostname(request.getServerName());
+           
+                    // explicitly-defined virtual hosts, most specific
+                    list=hosts.get(host);
+                    for (int j=0; j<LazyList.size(list); j++)
+                    {
+                        Handler handler = (Handler)LazyList.get(list,j);
+                        handler.handle(target,baseRequest, request, response);
+                        if (baseRequest.isHandled())
+                            return;
+                    }
+                    
+                    // wildcard for one level of names 
+                    list=hosts.get("*."+host.substring(host.indexOf(".")+1));
+                    for (int j=0; j<LazyList.size(list); j++)
+                    {
+                        Handler handler = (Handler)LazyList.get(list,j);
+                        handler.handle(target,baseRequest, request, response);
+                        if (baseRequest.isHandled())
+                            return;
+                    }
+                    
+                    // no virtualhosts defined for the context, least specific
+                    // will handle any request that does not match to a specific virtual host above
+                    list=hosts.get("*");
+                    for (int j=0; j<LazyList.size(list); j++)
+                    {
+                        Handler handler = (Handler)LazyList.get(list,j);
+                        handler.handle(target,baseRequest, request, response);
+                        if (baseRequest.isHandled())
+                            return;
+                    }
+                }
+                else
+                {
+                    for (int j=0; j<LazyList.size(list); j++)
+                    {
+                        Handler handler = (Handler)LazyList.get(list,j);
+                        handler.handle(target,baseRequest, request, response);
+                        if (baseRequest.isHandled())
+                            return;
+                    }
+                }
+	    }
+	}
+	else
+	{
+            // This may not work in all circumstances... but then I think it should never be called
+	    for (int i=0;i<handlers.length;i++)
+	    {
+		handlers[i].handle(target,baseRequest, request, response);
+		if ( baseRequest.isHandled())
+		    return;
+	    }
+	}
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add a context handler.
+     * @param contextPath  The context path to add
+     * @return the ContextHandler just added
+     */
+    public ContextHandler addContext(String contextPath,String resourceBase) 
+    {
+        try
+        {
+            ContextHandler context = _contextClass.newInstance();
+            context.setContextPath(contextPath);
+            context.setResourceBase(resourceBase);
+            addHandler(context);
+            return context;
+        }
+        catch (Exception e)
+        {
+            LOG.debug(e);
+            throw new Error(e);
+        }
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The class to use to add new Contexts
+     */
+    public Class getContextClass()
+    {
+        return _contextClass;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextClass The class to use to add new Contexts
+     */
+    public void setContextClass(Class contextClass)
+    {
+        if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
+            throw new IllegalArgumentException();
+        _contextClass = contextClass;
+    }
+    
+    /* ------------------------------------------------------------ */
+    private String normalizeHostname( String host )
+    {
+        if ( host == null )
+            return null;
+        
+        if ( host.endsWith( "." ) )
+            return host.substring( 0, host.length() -1);
+      
+        return host;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/DebugHandler.java b/src/java/org/eclipse/jetty/server/handler/DebugHandler.java
new file mode 100644
index 0000000..1bcc043
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/DebugHandler.java
@@ -0,0 +1,158 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler; 
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.RolloverFileOutputStream;
+
+
+/** 
+ * Debug Handler.
+ * A lightweight debug handler that can be used in production code.
+ * Details of the request and response are written to an output stream
+ * and the current thread name is updated with information that will link
+ * to the details in that output.
+ */
+public class DebugHandler extends HandlerWrapper
+{
+    private DateCache _date=new DateCache("HH:mm:ss", Locale.US);
+    private OutputStream _out;
+    private PrintStream _print;
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        final Response base_response = baseRequest.getResponse();
+        final Thread thread=Thread.currentThread();
+        final String old_name=thread.getName();
+
+        boolean suspend=false;
+        boolean retry=false;
+        String name=(String)request.getAttribute("org.eclipse.jetty.thread.name");
+        if (name==null)
+            name=old_name+":"+baseRequest.getScheme()+"://"+baseRequest.getLocalAddr()+":"+baseRequest.getLocalPort()+baseRequest.getUri();
+        else
+            retry=true;
+        
+        String ex=null;
+        try
+        {
+            final String d=_date.now();
+            final int ms=_date.lastMs();
+            
+            if (retry)
+                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" RETRY");
+            else
+                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+baseRequest.getRemoteAddr()+" "+request.getMethod()+" "+baseRequest.getHeader("Cookie")+"; "+baseRequest.getHeader("User-Agent"));
+            thread.setName(name);
+            
+            getHandler().handle(target,baseRequest,request,response);
+        }
+        catch(IOException ioe)
+        {
+            ex=ioe.toString();
+            throw ioe;
+        }
+        catch(ServletException se)
+        {
+            ex=se.toString()+":"+se.getCause();
+            throw se;
+        }
+        catch(RuntimeException rte)
+        {
+            ex=rte.toString();
+            throw rte;
+        }
+        catch(Error e)
+        {
+            ex=e.toString();
+            throw e;
+        }
+        finally
+        {
+            thread.setName(old_name);
+            final String d=_date.now();
+            final int ms=_date.lastMs();
+            suspend=baseRequest.getAsyncContinuation().isSuspended();
+            if (suspend)
+            {
+                request.setAttribute("org.eclipse.jetty.thread.name",name);
+                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" SUSPEND");
+            }
+            else
+                _print.println(d+(ms>99?".":(ms>9?".0":".00"))+ms+":"+name+" "+base_response.getStatus()+
+		        (ex==null?"":("/"+ex))+
+		        " "+base_response.getContentType()+" "+base_response.getContentCount());
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_out==null)
+            _out=new RolloverFileOutputStream("./logs/yyyy_mm_dd.debug.log",true);
+        _print=new PrintStream(_out);
+        super.doStart();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _print.close();
+    }
+
+    /**
+     * @return the out
+     */
+    public OutputStream getOutputStream()
+    {
+        return _out;
+    }
+
+    /**
+     * @param out the out to set
+     */
+    public void setOutputStream(OutputStream out)
+    {
+        _out = out;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/src/java/org/eclipse/jetty/server/handler/DefaultHandler.java
new file mode 100644
index 0000000..ee5a51c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/DefaultHandler.java
@@ -0,0 +1,208 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Default Handler.
+ * 
+ * This handle will deal with unhandled requests in the server.
+ * For requests for favicon.ico, the Jetty icon is served. 
+ * For reqests to '/' a 404 with a list of known contexts is served.
+ * For all other requests a normal 404 is served.
+ * TODO Implement OPTIONS and TRACE methods for the server.
+ * 
+ * 
+ * @org.apache.xbean.XBean
+ */
+public class DefaultHandler extends AbstractHandler
+{
+    private static final Logger LOG = Log.getLogger(DefaultHandler.class);
+
+    final long _faviconModified=(System.currentTimeMillis()/1000)*1000L;
+    byte[] _favicon;
+    boolean _serveIcon=true;
+    boolean _showContexts=true;
+    
+    public DefaultHandler()
+    {
+        try
+        {
+            URL fav = this.getClass().getClassLoader().getResource("org/eclipse/jetty/favicon.ico");
+            if (fav!=null)
+            {
+                Resource r = Resource.newResource(fav);
+                _favicon=IO.readBytes(r.getInputStream());
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {              
+        if (response.isCommitted() || baseRequest.isHandled())
+            return;
+        
+        baseRequest.setHandled(true);
+        
+        String method=request.getMethod();
+
+        // little cheat for common request
+        if (_serveIcon && _favicon!=null && method.equals(HttpMethods.GET) && request.getRequestURI().equals("/favicon.ico"))
+        {
+            if (request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE)==_faviconModified)
+                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+            else
+            {
+                response.setStatus(HttpServletResponse.SC_OK);
+                response.setContentType("image/x-icon");
+                response.setContentLength(_favicon.length);
+                response.setDateHeader(HttpHeaders.LAST_MODIFIED, _faviconModified);
+                response.setHeader(HttpHeaders.CACHE_CONTROL,"max-age=360000,public");
+                response.getOutputStream().write(_favicon);
+            }
+            return;
+        }
+        
+        
+        if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/"))
+        {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;   
+        }
+
+        response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+        response.setContentType(MimeTypes.TEXT_HTML);
+        
+        ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
+        
+        writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
+        writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
+        writer.write("No context on this server matched or handled this request.<BR>");
+        
+        if (_showContexts)
+        {
+            writer.write("Contexts known to this server are: <ul>");
+            
+            Server server = getServer();
+            Handler[] handlers = server==null?null:server.getChildHandlersByClass(ContextHandler.class);
+     
+            for (int i=0;handlers!=null && i<handlers.length;i++)
+            {
+                ContextHandler context = (ContextHandler)handlers[i];
+                if (context.isRunning())
+                {
+                    writer.write("<li><a href=\"");
+                    if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                        writer.write("http://"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                    writer.write(context.getContextPath());
+                    if (context.getContextPath().length()>1 && context.getContextPath().endsWith("/"))
+                        writer.write("/");
+                    writer.write("\">");
+                    writer.write(context.getContextPath());
+                    if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                        writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                    writer.write("&nbsp;--->&nbsp;");
+                    writer.write(context.toString());
+                    writer.write("</a></li>\n");
+                }
+                else
+                {
+                    writer.write("<li>");
+                    writer.write(context.getContextPath());
+                    if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
+                        writer.write("&nbsp;@&nbsp;"+context.getVirtualHosts()[0]+":"+request.getLocalPort());
+                    writer.write("&nbsp;--->&nbsp;");
+                    writer.write(context.toString());
+                    if (context.isFailed())
+                        writer.write(" [failed]");
+                    if (context.isStopped())
+                        writer.write(" [stopped]");
+                    writer.write("</li>\n");
+                }
+            }
+        }
+        
+        for (int i=0;i<10;i++)
+            writer.write("\n<!-- Padding for IE                  -->");
+        
+        writer.write("\n</BODY>\n</HTML>\n");
+        writer.flush();
+        response.setContentLength(writer.size());
+        OutputStream out=response.getOutputStream();
+        writer.writeTo(out);
+        out.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns true if the handle can server the jetty favicon.ico
+     */
+    public boolean getServeIcon()
+    {
+        return _serveIcon;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param serveIcon true if the handle can server the jetty favicon.ico
+     */
+    public void setServeIcon(boolean serveIcon)
+    {
+        _serveIcon = serveIcon;
+    }
+    
+    public boolean getShowContexts()
+    {
+        return _showContexts;
+    }
+
+    public void setShowContexts(boolean show)
+    {
+        _showContexts = show;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/src/java/org/eclipse/jetty/server/handler/ErrorHandler.java
new file mode 100644
index 0000000..8863449
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ErrorHandler.java
@@ -0,0 +1,285 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.ByteArrayISO8859Writer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handler for Error pages
+ * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or 
+ * {@link org.eclipse.jetty.server.Server#addBean(Object)}.   
+ * It is called by the HttpResponse.sendError method to write a error page.
+ * 
+ */
+public class ErrorHandler extends AbstractHandler
+{    
+    private static final Logger LOG = Log.getLogger(ErrorHandler.class);
+    public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
+    
+    boolean _showStacks=true;
+    boolean _showMessageInTitle=true;
+    String _cacheControl="must-revalidate,no-cache,no-store";
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
+        String method = request.getMethod();
+        if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD))
+        {
+            connection.getRequest().setHandled(true);
+            return;
+        }
+        
+        if (this instanceof ErrorPageMapper)
+        {
+            String error_page=((ErrorPageMapper)this).getErrorPage(request);
+            if (error_page!=null && request.getServletContext()!=null)
+            {
+                String old_error_page=(String)request.getAttribute(ERROR_PAGE);
+                if (old_error_page==null || !old_error_page.equals(error_page))
+                {
+                    request.setAttribute(ERROR_PAGE, error_page);
+
+                    Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page);
+                    try
+                    {
+                        if(dispatcher!=null)
+                        {
+                            dispatcher.error(request, response);
+                            return;
+                        }
+                        LOG.warn("No error page "+error_page);
+                    }
+                    catch (ServletException e)
+                    {
+                        LOG.warn(Log.EXCEPTION, e);
+                        return;
+                    }
+                }
+            }
+        }
+        
+        connection.getRequest().setHandled(true);
+        response.setContentType(MimeTypes.TEXT_HTML_8859_1);    
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeaders.CACHE_CONTROL, _cacheControl);
+        ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096);
+        handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason());
+        writer.flush();
+        response.setContentLength(writer.size());
+        writer.writeTo(response.getOutputStream());
+        writer.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+    {
+        writeErrorPage(request, writer, code, message, _showStacks);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        if (message == null)
+            message=HttpStatus.getMessage(code);
+
+        writer.write("<html>\n<head>\n");
+        writeErrorPageHead(request,writer,code,message);
+        writer.write("</head>\n<body>");
+        writeErrorPageBody(request,writer,code,message,showStacks);
+        writer.write("\n</body>\n</html>\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+        {
+        writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\"/>\n");
+        writer.write("<title>Error ");
+        writer.write(Integer.toString(code));
+
+        if (_showMessageInTitle)
+        {
+            writer.write(' ');
+            write(writer,message);
+        }
+        writer.write("</title>\n");    
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+        String uri= request.getRequestURI();
+        
+        writeErrorPageMessage(request,writer,code,message,uri);
+        if (showStacks)
+            writeErrorPageStacks(request,writer);
+        writer.write("<hr /><i><small>Powered by Jetty://</small></i>");
+        for (int i= 0; i < 20; i++)
+            writer.write("<br/>                                                \n");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri)
+    throws IOException
+    {
+        writer.write("<h2>HTTP ERROR ");
+        writer.write(Integer.toString(code));
+        writer.write("</h2>\n<p>Problem accessing ");
+        write(writer,uri);
+        writer.write(". Reason:\n<pre>    ");
+        write(writer,message);
+        writer.write("</pre></p>");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeErrorPageStacks(HttpServletRequest request, Writer writer)
+        throws IOException
+    {
+        Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+        while(th!=null)
+        {
+            writer.write("<h3>Caused by:</h3><pre>");
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            th.printStackTrace(pw);
+            pw.flush();
+            write(writer,sw.getBuffer().toString());
+            writer.write("</pre>\n");
+
+            th =th.getCause();
+        }
+    }
+        
+
+    /* ------------------------------------------------------------ */
+    /** Get the cacheControl.
+     * @return the cacheControl header to set on error responses.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the cacheControl.
+     * @param cacheControl the cacheControl header to set on error responses.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl = cacheControl;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if stack traces are shown in the error pages
+     */
+    public boolean isShowStacks()
+    {
+        return _showStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showStacks True if stack traces are shown in the error pages
+     */
+    public void setShowStacks(boolean showStacks)
+    {
+        _showStacks = showStacks;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param showMessageInTitle if true, the error message appears in page title
+     */
+    public void setShowMessageInTitle(boolean showMessageInTitle)
+    {
+        _showMessageInTitle = showMessageInTitle;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public boolean getShowMessageInTitle()
+    {
+        return _showMessageInTitle;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void write(Writer writer,String string)
+        throws IOException
+    {
+        if (string==null)
+            return;
+        
+        for (int i=0;i<string.length();i++)
+        {
+            char c=string.charAt(i);
+            
+            switch(c)
+            {
+                case '&' :
+                    writer.write("&amp;");
+                    break;
+                case '<' :
+                    writer.write("&lt;");
+                    break;
+                case '>' :
+                    writer.write("&gt;");
+                    break;
+                    
+                default:
+                    if (Character.isISOControl(c) && !Character.isWhitespace(c))
+                        writer.write('?');
+                    else 
+                        writer.write(c);
+            }          
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public interface ErrorPageMapper
+    {
+        String getErrorPage(HttpServletRequest request);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/GzipHandler.java b/src/java/org/eclipse/jetty/server/handler/GzipHandler.java
new file mode 100644
index 0000000..c0c3b68
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/GzipHandler.java
@@ -0,0 +1,356 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPOutputStream;
+
+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.HttpMethods;
+import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * GZIP Handler This handler will gzip the content of a response if:
+ * <ul>
+ * <li>The filter is mapped to a matching path</li>
+ * <li>The response status code is >=200 and <300
+ * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
+ * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or if no mimeTypes are defined the
+ * content-type is not "application/gzip"</li>
+ * <li>No content-encoding is specified by the resource</li>
+ * </ul>
+ * 
+ * <p>
+ * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
+ * then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
+ * </p>
+ */
+public class GzipHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(GzipHandler.class);
+
+    protected Set<String> _mimeTypes;
+    protected Set<String> _excluded;
+    protected int _bufferSize = 8192;
+    protected int _minGzipSize = 256;
+    protected String _vary = "Accept-Encoding, User-Agent";
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new gzip handler.
+     */
+    public GzipHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the mime types.
+     * 
+     * @return mime types to set
+     */
+    public Set<String> getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the mime types.
+     * 
+     * @param mimeTypes
+     *            the mime types to set
+     */
+    public void setMimeTypes(Set<String> mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the mime types.
+     * 
+     * @param mimeTypes
+     *            the mime types to set
+     */
+    public void setMimeTypes(String mimeTypes)
+    {
+        if (mimeTypes != null)
+        {
+            _mimeTypes = new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(mimeTypes,",",false);
+            while (tok.hasMoreTokens())
+            {
+                _mimeTypes.add(tok.nextToken());
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the excluded user agents.
+     * 
+     * @return excluded user agents
+     */
+    public Set<String> getExcluded()
+    {
+        return _excluded;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the excluded user agents.
+     * 
+     * @param excluded
+     *            excluded user agents to set
+     */
+    public void setExcluded(Set<String> excluded)
+    {
+        _excluded = excluded;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the excluded user agents.
+     * 
+     * @param excluded
+     *            excluded user agents to set
+     */
+    public void setExcluded(String excluded)
+    {
+        if (excluded != null)
+        {
+            _excluded = new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(excluded,",",false);
+            while (tok.hasMoreTokens())
+                _excluded.add(tok.nextToken());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The value of the Vary header set if a response can be compressed.
+     */
+    public String getVary()
+    {
+        return _vary;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the value of the Vary header sent with responses that could be compressed.  
+     * <p>
+     * By default it is set to 'Accept-Encoding, User-Agent' since IE6 is excluded by 
+     * default from the excludedAgents. If user-agents are not to be excluded, then 
+     * this can be set to 'Accept-Encoding'.  Note also that shared caches may cache 
+     * many copies of a resource that is varied by User-Agent - one per variation of the 
+     * User-Agent, unless the cache does some normalization of the UA string.
+     * @param vary The value of the Vary header set if a response can be compressed.
+     */
+    public void setVary(String vary)
+    {
+        _vary = vary;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the buffer size.
+     * 
+     * @return the buffer size
+     */
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the buffer size.
+     * 
+     * @param bufferSize
+     *            buffer size to set
+     */
+    public void setBufferSize(int bufferSize)
+    {
+        _bufferSize = bufferSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the minimum reponse size.
+     * 
+     * @return minimum reponse size
+     */
+    public int getMinGzipSize()
+    {
+        return _minGzipSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the minimum reponse size.
+     * 
+     * @param minGzipSize
+     *            minimum reponse size
+     */
+    public void setMinGzipSize(int minGzipSize)
+    {
+        _minGzipSize = minGzipSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler!=null && isStarted())
+        {
+            String ae = request.getHeader("accept-encoding");
+            if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding")
+                    && !HttpMethods.HEAD.equalsIgnoreCase(request.getMethod()))
+            {
+                if (_excluded!=null)
+                {
+                    String ua = request.getHeader("User-Agent");
+                    if (_excluded.contains(ua))
+                    {
+                        _handler.handle(target,baseRequest, request, response);
+                        return;
+                    }
+                }
+
+                final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response);
+                
+                boolean exceptional=true;
+                try
+                {
+                    _handler.handle(target, baseRequest, request, wrappedResponse);
+                    exceptional=false;
+                }
+                finally
+                {
+                    Continuation continuation = ContinuationSupport.getContinuation(request);
+                    if (continuation.isSuspended() && continuation.isResponseWrapped())   
+                    {
+                        continuation.addContinuationListener(new ContinuationListener()
+                        {
+                            public void onComplete(Continuation continuation)
+                            {
+                                try
+                                {
+                                    wrappedResponse.finish();
+                                }
+                                catch(IOException e)
+                                {
+                                    LOG.warn(e);
+                                }
+                            }
+
+                            public void onTimeout(Continuation continuation)
+                            {}
+                        });
+                    }
+                    else if (exceptional && !response.isCommitted())
+                    {
+                        wrappedResponse.resetBuffer();
+                        wrappedResponse.noCompression();
+                    }
+                    else
+                        wrappedResponse.finish();
+                }
+            }
+            else
+            {
+                _handler.handle(target,baseRequest, request, response);
+            }
+        }
+    }
+
+    /**
+     * Allows derived implementations to replace ResponseWrapper implementation.
+     *
+     * @param request the request
+     * @param response the response
+     * @return the gzip response wrapper
+     */
+    protected CompressedResponseWrapper newGzipResponseWrapper(HttpServletRequest request, HttpServletResponse response)
+    {
+        return new CompressedResponseWrapper(request,response)
+        {
+            {
+                super.setMimeTypes(GzipHandler.this._mimeTypes);
+                super.setBufferSize(GzipHandler.this._bufferSize);
+                super.setMinCompressSize(GzipHandler.this._minGzipSize);
+            }
+            
+            @Override
+            protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+            {
+                return new AbstractCompressedStream("gzip",request,this,_vary)
+                {
+                    @Override
+                    protected DeflaterOutputStream createStream() throws IOException
+                    {
+                        return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
+                    }
+                };
+            }
+            
+            @Override
+            protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+            {
+                return GzipHandler.this.newWriter(out,encoding);
+            }
+        };
+    }
+    
+    /**
+     * Allows derived implementations to replace PrintWriter implementation.
+     *
+     * @param out the out
+     * @param encoding the encoding
+     * @return the prints the writer
+     * @throws UnsupportedEncodingException
+     */
+    protected PrintWriter newWriter(OutputStream out,String encoding) throws UnsupportedEncodingException
+    {
+        return encoding==null?new PrintWriter(out):new PrintWriter(new OutputStreamWriter(out,encoding));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/src/java/org/eclipse/jetty/server/handler/HandlerCollection.java
new file mode 100644
index 0000000..282a8fc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/HandlerCollection.java
@@ -0,0 +1,316 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+
+/* ------------------------------------------------------------ */
+/** A collection of handlers.  
+ * <p>
+ * The default implementations  calls all handlers in list order, 
+ * regardless of the response status or exceptions. Derived implementation
+ * may alter the order or the conditions of calling the contained 
+ * handlers.
+ * <p>
+ * 
+ * @org.apache.xbean.XBean
+ */
+public class HandlerCollection extends AbstractHandlerContainer
+{
+    private final boolean _mutableWhenRunning;
+    private volatile Handler[] _handlers;
+    private boolean _parallelStart=false; 
+
+    /* ------------------------------------------------------------ */
+    public HandlerCollection()
+    {
+        _mutableWhenRunning=false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public HandlerCollection(boolean mutableWhenRunning)
+    {
+        _mutableWhenRunning=mutableWhenRunning;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler[] getHandlers()
+    {
+        return _handlers;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     * @param handlers The handlers to set.
+     */
+    public void setHandlers(Handler[] handlers)
+    {
+        if (!_mutableWhenRunning && isStarted())
+            throw new IllegalStateException(STARTED);
+        
+        Handler [] old_handlers = _handlers==null?null:_handlers.clone();
+        _handlers = handlers;
+        
+        Server server = getServer();
+        MultiException mex = new MultiException();
+        for (int i=0;handlers!=null && i<handlers.length;i++)
+        {
+            if (handlers[i].getServer()!=server)
+                handlers[i].setServer(server);
+        }
+
+        if (getServer()!=null)
+            getServer().getContainer().update(this, old_handlers, handlers, "handler");
+        
+        // stop old handlers
+        for (int i=0;old_handlers!=null && i<old_handlers.length;i++)
+        {
+            if (old_handlers[i]!=null)
+            {
+                try
+                {
+                    if (old_handlers[i].isStarted())
+                        old_handlers[i].stop();
+                }
+                catch (Throwable e)
+                {
+                    mex.add(e);
+                }
+            }
+        }
+                
+        mex.ifExceptionThrowRuntime();
+    }
+    
+
+    
+    /* ------------------------------------------------------------ */
+    /** Get the parrallelStart.
+     * @return true if the contained handlers are started in parallel.
+     */
+    public boolean isParallelStart()
+    {
+        return _parallelStart;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Set the parallelStart.
+     * @param parallelStart If true, contained handlers are started in parallel.
+     */
+    public void setParallelStart(boolean parallelStart)
+    {
+        this._parallelStart = parallelStart;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+        throws IOException, ServletException
+    {
+        if (_handlers!=null && isStarted())
+        {
+            MultiException mex=null;
+            
+            for (int i=0;i<_handlers.length;i++)
+            {
+                try
+                {
+                    _handlers[i].handle(target,baseRequest, request, response);
+                }
+                catch(IOException e)
+                {
+                    throw e;
+                }
+                catch(RuntimeException e)
+                {
+                    throw e;
+                }
+                catch(Exception e)
+                {
+                    if (mex==null)
+                        mex=new MultiException();
+                    mex.add(e);
+                }
+            }
+            if (mex!=null)
+            {
+                if (mex.size()==1)
+                    throw new ServletException(mex.getThrowable(0));
+                else
+                    throw new ServletException(mex);
+            }
+            
+        }    
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        final MultiException mex=new MultiException();
+        if (_handlers!=null)
+        {
+            if (_parallelStart)
+            {
+                final CountDownLatch latch = new CountDownLatch(_handlers.length);
+                final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+                for (int i=0;i<_handlers.length;i++)
+                {
+                    final int h=i;
+                    getServer().getThreadPool().dispatch(
+                            new Runnable()
+                            {
+                                public void run()
+                                {
+                                    ClassLoader orig = Thread.currentThread().getContextClassLoader();
+                                    try
+                                    {
+                                        Thread.currentThread().setContextClassLoader(loader);
+                                        _handlers[h].start();
+                                    }
+                                    catch(Throwable e)
+                                    {
+                                        mex.add(e);
+                                    }
+                                    finally
+                                    {
+                                        Thread.currentThread().setContextClassLoader(orig);
+                                        latch.countDown();
+                                    }
+                                }
+                            }
+                    );
+                }
+                latch.await();
+            }
+            else
+            {
+                for (int i=0;i<_handlers.length;i++)
+                {
+                    try{_handlers[i].start();}
+                    catch(Throwable e){mex.add(e);}
+                }
+            }
+        }
+        super.doStart();
+        mex.ifExceptionThrow();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.AbstractHandler#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        MultiException mex=new MultiException();
+        try { super.doStop(); } catch(Throwable e){mex.add(e);}
+        if (_handlers!=null)
+        {
+            for (int i=_handlers.length;i-->0;)
+                try{_handlers[i].stop();}catch(Throwable e){mex.add(e);}
+        }
+        mex.ifExceptionThrow();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+        
+        Server old_server=getServer();
+        
+        super.setServer(server);
+
+        Handler[] h=getHandlers();
+        for (int i=0;h!=null && i<h.length;i++)
+            h[i].setServer(server);
+        
+        if (server!=null && server!=old_server)
+            server.getContainer().update(this, null,_handlers, "handler");
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Add a handler.
+     * This implementation adds the passed handler to the end of the existing collection of handlers. 
+     * @see org.eclipse.jetty.server.server.HandlerContainer#addHandler(org.eclipse.jetty.server.server.Handler)
+     */
+    public void addHandler(Handler handler)
+    {
+        setHandlers((Handler[])LazyList.addToArray(getHandlers(), handler, Handler.class));
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void removeHandler(Handler handler)
+    {
+        Handler[] handlers = getHandlers();
+        
+        if (handlers!=null && handlers.length>0 )
+            setHandlers((Handler[])LazyList.removeFromArray(handlers, handler));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Object expandChildren(Object list, Class byClass)
+    {
+        Handler[] handlers = getHandlers();
+        for (int i=0;handlers!=null && i<handlers.length;i++)
+            list=expandHandler(handlers[i], list, byClass);
+        return list;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler[] children=getChildHandlers();
+        setHandlers(null);
+        for (Handler child: children)
+            child.destroy();
+        super.destroy();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/HandlerList.java b/src/java/org/eclipse/jetty/server/handler/HandlerList.java
new file mode 100644
index 0000000..6d7fc94
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/HandlerList.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+
+/* ------------------------------------------------------------ */
+/** HandlerList.
+ * This extension of {@link HandlerCollection} will call
+ * each contained handler in turn until either an exception is thrown, the response 
+ * is committed or a positive response status is set.
+ */
+public class HandlerList extends HandlerCollection
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * @see Handler#handle(String, Request, HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+        throws IOException, ServletException
+    {
+        Handler[] handlers = getHandlers();
+        
+        if (handlers!=null && isStarted())
+        {
+            for (int i=0;i<handlers.length;i++)
+            {
+                handlers[i].handle(target,baseRequest, request, response);
+                if ( baseRequest.isHandled())
+                    return;
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/src/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
new file mode 100644
index 0000000..e28eeb3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/HandlerWrapper.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** A <code>HandlerWrapper</code> acts as a {@link Handler} but delegates the {@link Handler#handle handle} method and
+ * {@link LifeCycle life cycle} events to a delegate. This is primarily used to implement the <i>Decorator</i> pattern.
+ *
+ */
+public class HandlerWrapper extends AbstractHandlerContainer
+{
+    protected Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     *
+     */
+    public HandlerWrapper()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler[] getHandlers()
+    {
+        if (_handler==null)
+            return new Handler[0];
+        return new Handler[] {_handler};
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        Handler old_handler = _handler;
+        _handler = handler;
+        if (handler!=null)
+            handler.setServer(getServer());
+        
+        if (getServer()!=null)
+            getServer().getContainer().update(this, old_handler, handler, "handler");
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_handler!=null)
+            _handler.start();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        if (_handler!=null)
+            _handler.stop();
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler!=null && isStarted())
+        {
+            _handler.handle(target,baseRequest, request, response);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        Server old_server=getServer();
+        if (server==old_server)
+            return;
+
+        if (isStarted())
+            throw new IllegalStateException(STARTED);
+
+        super.setServer(server);
+
+        Handler h=getHandler();
+        if (h!=null)
+            h.setServer(server);
+
+        if (server!=null && server!=old_server)
+            server.getContainer().update(this, null,_handler, "handler");
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected Object expandChildren(Object list, Class byClass)
+    {
+        return expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    public <H extends Handler> H getNestedHandlerByClass(Class<H> byclass)
+    {
+        HandlerWrapper h=this;
+        while (h!=null)
+        {
+            if (byclass.isInstance(h))
+                return (H)h;
+            Handler w = h.getHandler();
+            if (w instanceof HandlerWrapper)
+                h=(HandlerWrapper)w;
+            else break;
+        }
+        return null;
+
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child=getHandler();
+        if (child!=null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/HotSwapHandler.java b/src/java/org/eclipse/jetty/server/handler/HotSwapHandler.java
new file mode 100644
index 0000000..711744d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/HotSwapHandler.java
@@ -0,0 +1,176 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+
+/* ------------------------------------------------------------ */
+/**
+ * A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
+ * 
+ */
+public class HotSwapHandler extends AbstractHandlerContainer
+{
+    private volatile Handler _handler;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     */
+    public HotSwapHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler getHandler()
+    {
+        return _handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the handlers.
+     */
+    public Handler[] getHandlers()
+    {
+        return new Handler[]
+        { _handler };
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param handler
+     *            Set the {@link Handler} which should be wrapped.
+     */
+    public void setHandler(Handler handler)
+    {
+        if (handler == null)
+            throw new IllegalArgumentException("Parameter handler is null.");
+        try
+        {
+            Handler old_handler = _handler;
+            _handler = handler;
+            Server server = getServer();
+            handler.setServer(server);
+            addBean(handler);
+
+            if (server != null)
+                server.getContainer().update(this,old_handler,handler,"handler");
+
+            // if there is an old handler and it was started, stop it
+            if (old_handler != null)
+            {
+                removeBean(old_handler);
+            }
+
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.EventHandler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_handler != null && isStarted())
+        {
+            _handler.handle(target,baseRequest,request,response);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        Server old_server = getServer();
+        if (server == old_server)
+            return;
+
+        if (isRunning())
+            throw new IllegalStateException(RUNNING);
+
+        super.setServer(server);
+
+        Handler h = getHandler();
+        if (h != null)
+            h.setServer(server);
+
+        if (server != null && server != old_server)
+            server.getContainer().update(this,null,_handler,"handler");
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings(
+    { "rawtypes", "unchecked" })
+    @Override
+    protected Object expandChildren(Object list, Class byClass)
+    {
+        return expandHandler(_handler,list,byClass);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        if (!isStopped())
+            throw new IllegalStateException("!STOPPED");
+        Handler child = getHandler();
+        if (child != null)
+        {
+            setHandler(null);
+            child.destroy();
+        }
+        super.destroy();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/IPAccessHandler.java b/src/java/org/eclipse/jetty/server/handler/IPAccessHandler.java
new file mode 100644
index 0000000..46e5f1c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/IPAccessHandler.java
@@ -0,0 +1,382 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.IPAddressMap;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * IP Access Handler
+ * <p>
+ * Controls access to the wrapped handler by the real remote IP. Control is provided
+ * by white/black lists that include both internet addresses and URIs. This handler
+ * uses the real internet address of the connection, not one reported in the forwarded
+ * for headers, as this cannot be as easily forged. 
+ * <p>
+ * Typically, the black/white lists will be used in one of three modes:
+ * <ul>
+ * <li>Blocking a few specific IPs/URLs by specifying several black list entries.
+ * <li>Allowing only some specific IPs/URLs by specifying several white lists entries.
+ * <li>Allowing a general range of IPs/URLs by specifying several general white list
+ * entries, that are then further refined by several specific black list exceptions
+ * </ul>
+ * <p>
+ * An empty white list is treated as match all. If there is at least one entry in
+ * the white list, then a request must match a white list entry. Black list entries
+ * are always applied, so that even if an entry matches the white list, a black list 
+ * entry will override it.
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of 
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values, 
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ * <p>
+ * Internet address specification is separated from the URI pattern using the "|" (pipe)
+ * character. URI patterns follow the servlet specification for simple * prefix and 
+ * suffix wild cards (e.g. /, /foo, /foo/bar, /foo/bar/*, *.baz).
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the 
+ * internet address. Both of these features have been deprecated in the current version. 
+ * <p>
+ * Examples of the entry specifications are:
+ * <ul>
+ * <li>10.10.1.2 - all requests from IP 10.10.1.2
+ * <li>10.10.1.2|/foo/bar - all requests from IP 10.10.1.2 to URI /foo/bar
+ * <li>10.10.1.2|/foo/* - all requests from IP 10.10.1.2 to URIs starting with /foo/
+ * <li>10.10.1.2|*.html - all requests from IP 10.10.1.2 to URIs ending with .html
+ * <li>10.10.0-255.0-255 - all requests from IPs within 10.10.0.0/16 subnet
+ * <li>10.10.0-.-255|/foo/bar - all requests from IPs within 10.10.0.0/16 subnet to URI /foo/bar
+ * <li>10.10.0-3,1,3,7,15|/foo/* - all requests from IPs addresses with last octet equal
+ *                                  to 1,3,7,15 in subnet 10.10.0.0/22 to URIs starting with /foo/
+ * </ul>
+ * <p>
+ * Earlier versions of the handler used internet address prefix wildcard specification
+ * to define a range of the internet addresses (e.g. 127., 10.10., 172.16.1.).
+ * They also used the first "/" character of the URI pattern to separate it from the 
+ * internet address. Both of these features have been deprecated in the current version. 
+ */
+public class IPAccessHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(IPAccessHandler.class);
+
+    IPAddressMap<PathMap> _white = new IPAddressMap<PathMap>();
+    IPAddressMap<PathMap> _black = new IPAddressMap<PathMap>();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object
+     */
+    public IPAccessHandler()
+    {
+        super();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Creates new handler object and initializes white- and black-list
+     * 
+     * @param white array of whitelist entries
+     * @param black array of blacklist entries
+     */
+    public IPAccessHandler(String[] white, String []black)
+    {
+        super();
+        
+        if (white != null && white.length > 0)
+            setWhite(white);
+        if (black != null && black.length > 0)
+            setBlack(black);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a whitelist entry to an existing handler configuration
+     * 
+     * @param entry new whitelist entry
+     */
+    public void addWhite(String entry)
+    {
+        add(entry, _white);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a blacklist entry to an existing handler configuration
+     * 
+     * @param entry new blacklist entry
+     */
+    public void addBlack(String entry)
+    {
+        add(entry, _black);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the whitelist of existing handler object
+     * 
+     * @param entries array of whitelist entries
+     */
+    public void setWhite(String[] entries)
+    {
+        set(entries, _white);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Re-initialize the blacklist of existing handler object
+     * 
+     * @param entries array of blacklist entries
+     */
+    public void setBlack(String[] entries)
+    {
+        set(entries, _black);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Checks the incoming request against the whitelist and blacklist
+     * 
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the real remote IP (not the one set by the forwarded headers (which may be forged))
+        AbstractHttpConnection connection = baseRequest.getConnection();
+        if (connection!=null)
+        {
+            EndPoint endp=connection.getEndPoint();
+            if (endp!=null)
+            {
+                String addr = endp.getRemoteAddr();
+                if (addr!=null && !isAddrUriAllowed(addr,baseRequest.getPathInfo()))
+                {
+                    response.sendError(HttpStatus.FORBIDDEN_403);
+                    baseRequest.setHandled(true);
+                    return;
+                }
+            }
+        }
+        
+        getHandler().handle(target,baseRequest, request, response);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to parse the new entry and add it to 
+     * the specified address pattern map.
+     * 
+     * @param entry new entry
+     * @param patternMap target address pattern map
+     */
+    protected void add(String entry, IPAddressMap<PathMap> patternMap)
+    {
+        if (entry != null && entry.length() > 0)
+        {
+            boolean deprecated = false;
+            int idx;
+            if (entry.indexOf('|') > 0 )
+            {
+                idx = entry.indexOf('|');
+            }
+            else
+            {
+                idx = entry.indexOf('/');
+                deprecated = (idx >= 0);
+            }
+            
+            String addr = idx > 0 ? entry.substring(0,idx) : entry;        
+            String path = idx > 0 ? entry.substring(idx) : "/*";
+            
+            if (addr.endsWith("."))
+                deprecated = true;
+            if (path!=null && (path.startsWith("|") || path.startsWith("/*.")))
+                path=path.substring(1);
+           
+            PathMap pathMap = patternMap.get(addr);
+            if (pathMap == null)
+            {
+                pathMap = new PathMap(true);
+                patternMap.put(addr,pathMap);
+            }
+            if (path != null && !"".equals(path))
+                pathMap.put(path,path);
+            
+            if (deprecated)
+                LOG.debug(toString() +" - deprecated specification syntax: "+entry);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper method to process a list of new entries and replace 
+     * the content of the specified address pattern map
+     * 
+     * @param entries new entries
+     * @param patternMap target address pattern map
+     */
+    protected void set(String[] entries,  IPAddressMap<PathMap> patternMap)
+    {
+        patternMap.clear();
+        
+        if (entries != null && entries.length > 0)
+        {
+            for (String addrPath:entries)
+            {
+                add(addrPath, patternMap);
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if specified request is allowed by current IPAccess rules.
+     * 
+     * @param addr internet address
+     * @param path context path
+     * @return true if request is allowed
+     *
+     */
+    protected boolean isAddrUriAllowed(String addr, String path)
+    {
+        if (_white.size()>0)
+        {
+            boolean match = false;
+            
+            Object whiteObj = _white.getLazyMatches(addr);
+            if (whiteObj != null) 
+            {
+                List whiteList = (whiteObj instanceof List) ? (List)whiteObj : Collections.singletonList(whiteObj);
+
+                for (Object entry: whiteList)
+                {
+                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
+                    if (match = (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null)))
+                        break;
+                }
+            }
+            
+            if (!match)
+                return false;
+        }
+
+        if (_black.size() > 0)
+        {
+            Object blackObj = _black.getLazyMatches(addr);
+            if (blackObj != null) 
+            {
+                List blackList = (blackObj instanceof List) ? (List)blackObj : Collections.singletonList(blackObj);
+    
+                for (Object entry: blackList)
+                {
+                    PathMap pathMap = ((Map.Entry<String,PathMap>)entry).getValue();
+                    if (pathMap!=null && (pathMap.size()==0 || pathMap.match(path)!=null))
+                        return false;
+                }
+            }
+        }
+        
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump the white- and black-list configurations when started
+     * 
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart()
+        throws Exception
+    {
+        super.doStart();
+        
+        if (LOG.isDebugEnabled())
+        {
+            System.err.println(dump());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump the handler configuration
+     */
+    public String dump()
+    {
+        StringBuilder buf = new StringBuilder();
+        
+        buf.append(toString());
+        buf.append(" WHITELIST:\n");
+        dump(buf, _white);
+        buf.append(toString());
+        buf.append(" BLACKLIST:\n");
+        dump(buf, _black);
+        
+        return buf.toString();
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Dump a pattern map into a StringBuilder buffer
+     * 
+     * @param buf buffer
+     * @param patternMap pattern map to dump
+     */
+    protected void dump(StringBuilder buf, IPAddressMap<PathMap> patternMap)
+    {
+        for (String addr: patternMap.keySet())
+        {
+            for (Object path: ((PathMap)patternMap.get(addr)).values())
+            {
+                buf.append("# ");
+                buf.append(addr);
+                buf.append("|");
+                buf.append(path);
+                buf.append("\n");
+            }       
+        }
+    }
+ }
diff --git a/src/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java b/src/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
new file mode 100644
index 0000000..f06a4e8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/IdleTimeoutHandler.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * Handler to adjust the idle timeout of requests while dispatched.
+ * 
+ * <p>Can be applied in jetty.xml with
+ * <pre>
+ *   &lt;Get id='handler' name='Handler'/>
+ *   &lt;Set name='Handler'>
+ *     &lt;New id='idleTimeoutHandler' class='org.eclipse.jetty.server.handler.IdleTimeoutHandler'>
+ *       &lt;Set name='Handler'>&lt;Ref id='handler'/>&lt;/Set>
+ *       &lt;Set name='IdleTimeoutMs'>5000&lt;/Set>
+ *     &lt;/New>
+ *   &lt;/Set>
+ * </pre>
+ */
+public class IdleTimeoutHandler extends HandlerWrapper
+{
+    private int _idleTimeoutMs = 1000;
+    private boolean _applyToAsync = false;
+    
+    
+    public boolean isApplyToAsync()
+    {
+        return _applyToAsync;
+    }
+
+    /**
+     * Should the adjusted idle time be maintained for asynchronous requests
+     * @param applyToAsync true if alternate idle timeout is applied to asynchronous requests
+     */
+    public void setApplyToAsync(boolean applyToAsync)
+    {
+        _applyToAsync = applyToAsync;
+    }
+
+    public long getIdleTimeoutMs()
+    {
+        return _idleTimeoutMs;
+    }
+
+    /**
+     * @param idleTimeoutMs The idle timeout in MS to apply while dispatched or async
+     */
+    public void setIdleTimeoutMs(int _idleTimeoutMs)
+    {
+        this._idleTimeoutMs = _idleTimeoutMs;
+    }
+    
+   
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection();
+        final EndPoint endp = connection==null?null:connection.getEndPoint();
+        
+        final int idle_timeout;
+        if (endp==null)
+            idle_timeout=-1;
+        else
+        {
+            idle_timeout=endp.getMaxIdleTime();
+            endp.setMaxIdleTime(_idleTimeoutMs);
+        }
+        
+        try
+        {
+            super.handle(target,baseRequest,request,response);
+        }
+        finally
+        {
+            if (endp!=null)
+            {
+                if (_applyToAsync && request.isAsyncStarted())
+                {
+                    request.getAsyncContext().addListener(new AsyncListener()
+                    {
+                        @Override
+                        public void onTimeout(AsyncEvent event) throws IOException
+                        {                            
+                        }
+                        
+                        @Override
+                        public void onStartAsync(AsyncEvent event) throws IOException
+                        {
+                        }
+                        
+                        @Override
+                        public void onError(AsyncEvent event) throws IOException
+                        {
+                            endp.setMaxIdleTime(idle_timeout);
+                        }
+                        
+                        @Override
+                        public void onComplete(AsyncEvent event) throws IOException
+                        {
+                            endp.setMaxIdleTime(idle_timeout);
+                        }
+                    });
+                }
+                else 
+                {
+                    endp.setMaxIdleTime(idle_timeout);
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/MovedContextHandler.java b/src/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
new file mode 100644
index 0000000..410454c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/MovedContextHandler.java
@@ -0,0 +1,154 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.URIUtil;
+
+/* ------------------------------------------------------------ */
+/** Moved ContextHandler.
+ * This context can be used to replace a context that has changed
+ * location.  Requests are redirected (either to a fixed URL or to a
+ * new context base). 
+ */
+public class MovedContextHandler extends ContextHandler
+{
+    final Redirector _redirector;
+    String _newContextURL;
+    boolean _discardPathInfo;
+    boolean _discardQuery;
+    boolean _permanent;
+    String _expires;
+
+    public MovedContextHandler()
+    {
+        _redirector=new Redirector();
+        setHandler(_redirector);
+        setAllowNullPathInfo(true);
+    }
+    
+    public MovedContextHandler(HandlerContainer parent, String contextPath, String newContextURL)
+    {
+        super(parent,contextPath);
+        _newContextURL=newContextURL;
+        _redirector=new Redirector();
+        setHandler(_redirector);
+    }
+
+    public boolean isDiscardPathInfo()
+    {
+        return _discardPathInfo;
+    }
+
+    public void setDiscardPathInfo(boolean discardPathInfo)
+    {
+        _discardPathInfo = discardPathInfo;
+    }
+
+    public String getNewContextURL()
+    {
+        return _newContextURL;
+    }
+
+    public void setNewContextURL(String newContextURL)
+    {
+        _newContextURL = newContextURL;
+    }
+
+    public boolean isPermanent()
+    {
+        return _permanent;
+    }
+
+    public void setPermanent(boolean permanent)
+    {
+        _permanent = permanent;
+    }
+
+    public boolean isDiscardQuery()
+    {
+        return _discardQuery;
+    }
+
+    public void setDiscardQuery(boolean discardQuery)
+    {
+        _discardQuery = discardQuery;
+    }
+    
+    private class Redirector extends AbstractHandler
+    {
+        public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+        {
+            if (_newContextURL==null)
+                return;
+
+            String path=_newContextURL;
+            if (!_discardPathInfo && request.getPathInfo()!=null)
+                path=URIUtil.addPaths(path, request.getPathInfo());
+            
+            StringBuilder location = URIUtil.hasScheme(path)?new StringBuilder():baseRequest.getRootURL();
+
+            location.append(path);
+            if (!_discardQuery && request.getQueryString()!=null)
+            {
+                location.append('?');
+                String q=request.getQueryString();
+                q=q.replaceAll("\r\n?&=","!");
+                location.append(q);
+            }
+            
+            response.setHeader(HttpHeaders.LOCATION,location.toString());
+
+            if (_expires!=null)
+                response.setHeader(HttpHeaders.EXPIRES,_expires);
+            
+            response.setStatus(_permanent?HttpServletResponse.SC_MOVED_PERMANENTLY:HttpServletResponse.SC_FOUND);
+            response.setContentLength(0);
+            baseRequest.setHandled(true);
+        }
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the expires header value or null if no expires header
+     */
+    public String getExpires()
+    {
+        return _expires;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param expires the expires header value or null if no expires header
+     */
+    public void setExpires(String expires)
+    {
+        _expires = expires;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ProxyHandler.java b/src/java/org/eclipse/jetty/server/handler/ProxyHandler.java
new file mode 100644
index 0000000..a5f82ca
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ProxyHandler.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import org.eclipse.jetty.server.Handler;
+
+
+/* ------------------------------------------------------------ */
+/** ProxyHandler.
+ * <p>This class has been renamed to ConnectHandler, as it only implements
+ * the CONNECT method (and a ProxyServlet must be used for full proxy handling).
+ * @deprecated Use {@link ConnectHandler}
+ */
+public class ProxyHandler extends ConnectHandler
+{
+    public ProxyHandler()
+    {
+        super();
+    }
+
+    public ProxyHandler(Handler handler, String[] white, String[] black)
+    {
+        super(handler,white,black);
+    }
+
+    public ProxyHandler(Handler handler)
+    {
+        super(handler);
+    }
+
+    public ProxyHandler(String[] white, String[] black)
+    {
+        super(white,black);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/src/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
new file mode 100644
index 0000000..e80c5f3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/RequestLogHandler.java
@@ -0,0 +1,192 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler; 
+
+import java.io.IOException;
+
+import javax.servlet.DispatcherType;
+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.server.AsyncContinuation;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/** 
+ * RequestLogHandler.
+ * This handler can be used to wrap an individual context for context logging.
+ * 
+ * @org.apache.xbean.XBean
+ */
+public class RequestLogHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(RequestLogHandler.class);
+
+    private RequestLog _requestLog;
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void handle(String target, final Request baseRequest, HttpServletRequest request, final HttpServletResponse response)
+            throws IOException, ServletException
+    {
+        AsyncContinuation continuation = baseRequest.getAsyncContinuation();
+        if (!continuation.isInitial())
+        {
+            baseRequest.setDispatchTime(System.currentTimeMillis());
+        }
+        
+        try
+        {
+            super.handle(target, baseRequest, request, response);
+        }
+        finally
+        {
+            if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST))
+            {
+                if (continuation.isAsync())
+                {
+                    if (continuation.isInitial())
+                        continuation.addContinuationListener(new ContinuationListener()
+                        {
+
+                            public void onTimeout(Continuation continuation)
+                            {
+
+                            }
+
+                            public void onComplete(Continuation continuation)
+                            {
+                                _requestLog.log(baseRequest, (Response)response);
+                            }
+                        });
+                }
+                else
+                    _requestLog.log(baseRequest, (Response)response);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequestLog(RequestLog requestLog)
+    {
+        //are we changing the request log impl?
+        try
+        {
+            if (_requestLog != null)
+                _requestLog.stop();
+        }
+        catch (Exception e)
+        {
+            LOG.warn (e);
+        }
+        
+        if (getServer()!=null)
+            getServer().getContainer().update(this, _requestLog, requestLog, "logimpl",true);
+        
+        _requestLog = requestLog;
+        
+        //if we're already started, then start our request log
+        try
+        {
+            if (isStarted() && (_requestLog != null))
+                _requestLog.start();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException (e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#setServer(org.eclipse.jetty.server.server.Server)
+     */
+    @Override
+    public void setServer(Server server)
+    {
+        if (_requestLog!=null)
+        {
+            if (getServer()!=null && getServer()!=server)
+                getServer().getContainer().update(this, _requestLog, null, "logimpl",true);
+            super.setServer(server);
+            if (server!=null && server!=getServer())
+                server.getContainer().update(this, null,_requestLog, "logimpl",true);
+        }
+        else
+            super.setServer(server);
+    }
+
+    /* ------------------------------------------------------------ */
+    public RequestLog getRequestLog() 
+    {
+        return _requestLog;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_requestLog==null)
+        {
+            LOG.warn("!RequestLog");
+            _requestLog=new NullRequestLog();
+        }
+        super.doStart();
+        _requestLog.start();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.server.handler.HandlerWrapper#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _requestLog.stop();
+        if (_requestLog instanceof NullRequestLog)
+            _requestLog=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullRequestLog extends AbstractLifeCycle implements RequestLog
+    {
+        public void log(Request request, Response response)
+        {            
+        }
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/src/java/org/eclipse/jetty/server/handler/ResourceHandler.java
new file mode 100644
index 0000000..11a3f2a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ResourceHandler.java
@@ -0,0 +1,545 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler.Context;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Resource Handler.
+ *
+ * This handle will serve static content and handle If-Modified-Since headers.
+ * No caching is done.
+ * Requests for resources that do not exist are let pass (Eg no 404's).
+ *
+ *
+ * @org.apache.xbean.XBean
+ */
+public class ResourceHandler extends HandlerWrapper
+{
+    private static final Logger LOG = Log.getLogger(ResourceHandler.class);
+
+    ContextHandler _context;
+    Resource _baseResource;
+    Resource _defaultStylesheet;
+    Resource _stylesheet;
+    String[] _welcomeFiles={"index.html"};
+    MimeTypes _mimeTypes = new MimeTypes();
+    ByteArrayBuffer _cacheControl;
+    boolean _aliases;
+    boolean _directory;
+    boolean _etags;
+
+    /* ------------------------------------------------------------ */
+    public ResourceHandler()
+    {
+    	
+    }
+
+    /* ------------------------------------------------------------ */
+    public MimeTypes getMimeTypes()
+    {
+        return _mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setMimeTypes(MimeTypes mimeTypes)
+    {
+        _mimeTypes = mimeTypes;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if resource aliases are allowed.
+     */
+    public boolean isAliases()
+    {
+        return _aliases;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if resource aliases (eg symlink, 8.3 names, case insensitivity) are allowed.
+     * Allowing aliases can significantly increase security vulnerabilities.
+     * If this handler is deployed inside a ContextHandler, then the
+     * {@link ContextHandler#isAliases()} takes precedent.
+     * @param aliases True if aliases are supported.
+     */
+    public void setAliases(boolean aliases)
+    {
+        _aliases = aliases;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the directory option.
+     * @return true if directories are listed.
+     */
+    public boolean isDirectoriesListed()
+    {
+        return _directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the directory.
+     * @param directory true if directories are listed.
+     */
+    public void setDirectoriesListed(boolean directory)
+    {
+        _directory = directory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if ETag processing is done
+     */
+    public boolean isEtags()
+    {
+        return _etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param etags True if ETag processing is done
+     */
+    public void setEtags(boolean etags)
+    {
+        _etags = etags;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+    throws Exception
+    {
+        Context scontext = ContextHandler.getCurrentContext();
+        _context = (scontext==null?null:scontext.getContextHandler());
+
+        if (_context!=null)
+            _aliases=_context.isAliases();
+
+        if (!_aliases && !FileResource.getCheckAliases())
+            throw new IllegalStateException("Alias checking disabled");
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the resourceBase.
+     */
+    public Resource getBaseResource()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the base resource as a string.
+     */
+    public String getResourceBase()
+    {
+        if (_baseResource==null)
+            return null;
+        return _baseResource.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param base The resourceBase to set.
+     */
+    public void setBaseResource(Resource base)
+    {
+        _baseResource=base;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resourceBase The base resource as a string.
+     */
+    public void setResourceBase(String resourceBase)
+    {
+        try
+        {
+            setBaseResource(Resource.newResource(resourceBase));
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(resourceBase);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the stylesheet as a Resource.
+     */
+    public Resource getStylesheet()
+    {
+    	if(_stylesheet != null)
+    	{
+    	    return _stylesheet;
+    	}
+    	else
+    	{
+    	    if(_defaultStylesheet == null)
+    	    {
+    	        try
+    	        {
+    	            _defaultStylesheet =  Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+    	        }
+    	        catch(IOException e)
+    	        {
+    	            LOG.warn(e.toString());
+    	            LOG.debug(e);
+    	        }	 
+    	    }
+    	    return _defaultStylesheet;
+    	}
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param stylesheet The location of the stylesheet to be used as a String.
+     */
+    public void setStylesheet(String stylesheet)
+    {
+        try
+        {
+            _stylesheet = Resource.newResource(stylesheet);
+            if(!_stylesheet.exists())
+            {
+                LOG.warn("unable to find custom stylesheet: " + stylesheet);
+                _stylesheet = null;
+            }
+        }
+    	catch(Exception e)
+    	{
+    		LOG.warn(e.toString());
+            LOG.debug(e);
+            throw new IllegalArgumentException(stylesheet.toString());
+    	}
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the cacheControl header to set on all static content.
+     */
+    public String getCacheControl()
+    {
+        return _cacheControl.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cacheControl the cacheControl header to set on all static content.
+     */
+    public void setCacheControl(String cacheControl)
+    {
+        _cacheControl=cacheControl==null?null:new ByteArrayBuffer(cacheControl);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     */
+    public Resource getResource(String path) throws MalformedURLException
+    {
+        if (path==null || !path.startsWith("/"))
+            throw new MalformedURLException(path);
+
+        Resource base = _baseResource;
+        if (base==null)
+        {
+            if (_context==null)
+                return null;
+            base=_context.getBaseResource();
+            if (base==null)
+                return null;
+        }
+
+        try
+        {
+            path=URIUtil.canonicalPath(path);
+            return base.addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getResource(HttpServletRequest request) throws MalformedURLException
+    {
+        String servletPath;
+        String pathInfo;
+        Boolean included = request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI) != null;
+        if (included != null && included.booleanValue())
+        {
+            servletPath = (String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+ 
+            if (servletPath == null && pathInfo == null)
+            {
+                servletPath = request.getServletPath();
+                pathInfo = request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        return getResource(pathInContext);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String[] getWelcomeFiles()
+    {
+        return _welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setWelcomeFiles(String[] welcomeFiles)
+    {
+        _welcomeFiles=welcomeFiles;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException
+    {
+        for (int i=0;i<_welcomeFiles.length;i++)
+        {
+            Resource welcome=directory.addPath(_welcomeFiles[i]);
+            if (welcome.exists() && !welcome.isDirectory())
+                return welcome;
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (baseRequest.isHandled())
+            return;
+
+        boolean skipContentBody = false;
+
+        if(!HttpMethods.GET.equals(request.getMethod()))
+        {
+            if(!HttpMethods.HEAD.equals(request.getMethod()))
+            {
+                //try another handler
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+            skipContentBody = true;
+        }
+        
+        Resource resource = getResource(request);
+        
+        if (resource==null || !resource.exists())
+        {
+            if (target.endsWith("/jetty-dir.css"))
+            {	                
+                resource = getStylesheet();
+                if (resource==null)
+                    return;
+                response.setContentType("text/css");
+            }
+            else 
+            {
+                //no resource - try other handlers
+                super.handle(target, baseRequest, request, response);
+                return;
+            }
+        }
+            
+        if (!_aliases && resource.getAlias()!=null)
+        {
+            LOG.info(resource+" aliased to "+resource.getAlias());
+            return;
+        }
+
+        // We are going to serve something
+        baseRequest.setHandled(true);
+
+        if (resource.isDirectory())
+        {
+            if (!request.getPathInfo().endsWith(URIUtil.SLASH))
+            {
+                response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH)));
+                return;
+            }
+
+            Resource welcome=getWelcome(resource);
+            if (welcome!=null && welcome.exists())
+                resource=welcome;
+            else
+            {
+                doDirectory(request,response,resource);
+                baseRequest.setHandled(true);
+                return;
+            }
+        }
+
+        // set some headers
+        long last_modified=resource.lastModified();
+        String etag=null;
+        if (_etags)
+        {
+            // simple handling of only a single etag
+            String ifnm = request.getHeader(HttpHeaders.IF_NONE_MATCH);
+            etag=resource.getWeakETag();
+            if (ifnm!=null && resource!=null && ifnm.equals(etag))
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
+                return;
+            }
+        }
+        
+        
+        if (last_modified>0)
+        {
+            long if_modified=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+            if (if_modified>0 && last_modified/1000<=if_modified/1000)
+            {
+                response.setStatus(HttpStatus.NOT_MODIFIED_304);
+                return;
+            }
+        }
+
+        Buffer mime=_mimeTypes.getMimeByExtension(resource.toString());
+        if (mime==null)
+            mime=_mimeTypes.getMimeByExtension(request.getPathInfo());
+
+        // set the headers
+        doResponseHeaders(response,resource,mime!=null?mime.toString():null);
+        response.setDateHeader(HttpHeaders.LAST_MODIFIED,last_modified);
+        if (_etags)
+            baseRequest.getResponse().getHttpFields().put(HttpHeaders.ETAG_BUFFER,etag);
+        
+        if(skipContentBody)
+            return;
+        // Send the content
+        OutputStream out =null;
+        try {out = response.getOutputStream();}
+        catch(IllegalStateException e) {out = new WriterOutputStream(response.getWriter());}
+
+        // See if a short direct method can be used?
+        if (out instanceof AbstractHttpConnection.Output)
+        {
+            // TODO file mapped buffers
+            ((AbstractHttpConnection.Output)out).sendContent(resource.getInputStream());
+        }
+        else
+        {
+            // Write content normally
+            resource.writeTo(out,0,resource.length());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void doDirectory(HttpServletRequest request,HttpServletResponse response, Resource resource)
+        throws IOException
+    {
+        if (_directory)
+        {
+            String listing = resource.getListHTML(request.getRequestURI(),request.getPathInfo().lastIndexOf("/") > 0);
+            response.setContentType("text/html; charset=UTF-8");
+            response.getWriter().println(listing);
+        }
+        else
+            response.sendError(HttpStatus.FORBIDDEN_403);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the response headers.
+     * This method is called to set the response headers such as content type and content length.
+     * May be extended to add additional headers.
+     * @param response
+     * @param resource
+     * @param mimeType
+     */
+    protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType)
+    {
+        if (mimeType!=null)
+            response.setContentType(mimeType);
+
+        long length=resource.length();
+
+        if (response instanceof Response)
+        {
+            HttpFields fields = ((Response)response).getHttpFields();
+
+            if (length>0)
+                fields.putLongField(HttpHeaders.CONTENT_LENGTH_BUFFER,length);
+
+            if (_cacheControl!=null)
+                fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
+        }
+        else
+        {
+            if (length>0)
+                response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(length));
+
+            if (_cacheControl!=null)
+                response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
+        }
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ScopedHandler.java b/src/java/org/eclipse/jetty/server/handler/ScopedHandler.java
new file mode 100644
index 0000000..0acfff2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ScopedHandler.java
@@ -0,0 +1,193 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+
+
+/* ------------------------------------------------------------ */
+/** ScopedHandler.
+ * 
+ * A ScopedHandler is a HandlerWrapper where the wrapped handlers
+ * each define a scope.   When {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)}
+ * is called on the first ScopedHandler in a chain of HandlerWrappers,
+ * the {@link #doScope(String, Request, HttpServletRequest, HttpServletResponse)} method is 
+ * called on all contained ScopedHandlers, before the 
+ * {@link #doHandle(String, Request, HttpServletRequest, HttpServletResponse)} method 
+ * is called on all contained handlers.
+ * 
+ * <p>For example if Scoped handlers A, B & C were chained together, then 
+ * the calling order would be:<pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *              C.doHandle(...)   
+ * <pre>
+ * 
+ * <p>If non scoped handler X was in the chained A, B, X & C, then 
+ * the calling order would be:<pre>
+ * A.handle(...)
+ *   A.doScope(...)
+ *     B.doScope(...)
+ *       C.doScope(...)
+ *         A.doHandle(...)
+ *           B.doHandle(...)
+ *             X.handle(...)
+ *               C.handle(...)
+ *                 C.doHandle(...)   
+ * <pre>
+ * 
+ * <p>A typical usage pattern is:<pre>
+ *     private static class MyHandler extends ScopedHandler
+ *     {
+ *         public void doScope(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 setUpMyScope();
+ *                 super.doScope(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 tearDownMyScope();
+ *             }
+ *         }
+ *         
+ *         public void doHandle(String target, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ *         {
+ *             try
+ *             {
+ *                 doMyHandling();
+ *                 super.doHandle(target,request,response);
+ *             }
+ *             finally
+ *             {
+ *                 cleanupMyHandling();
+ *             }
+ *         }
+ *     }
+ * </pre>
+ */
+public abstract class ScopedHandler extends HandlerWrapper
+{
+    private static final ThreadLocal<ScopedHandler> __outerScope= new ThreadLocal<ScopedHandler>();
+    protected ScopedHandler _outerScope;
+    protected ScopedHandler _nextScope;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.HandlerWrapper#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        try
+        {
+            _outerScope=__outerScope.get();
+            if (_outerScope==null)
+                __outerScope.set(this);
+            
+            super.doStart();
+            
+            _nextScope= (ScopedHandler)getChildHandlerByClass(ScopedHandler.class);
+            
+        }
+        finally
+        {
+            if (_outerScope==null)
+                __outerScope.set(null);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* 
+     */
+    @Override
+    public final void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_outerScope==null)  
+            doScope(target,baseRequest,request, response);
+        else 
+            doHandle(target,baseRequest,request, response);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * Scope the handler
+     */
+    public abstract void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+        throws IOException, ServletException;
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * Scope the handler
+     */
+    public final void nextScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+        throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null)
+            _nextScope.doScope(target,baseRequest,request, response);
+        else if (_outerScope!=null)
+            _outerScope.doHandle(target,baseRequest,request, response);
+        else 
+            doHandle(target,baseRequest,request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * Do the handler work within the scope.
+     */
+    public abstract void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 
+        throws IOException, ServletException;
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * Do the handler work within the scope.
+     */
+    public final void nextHandle(String target, final Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // this method has been manually inlined in several locations, but
+        // is called protected by an if(never()), so your IDE can find those
+        // locations if this code is changed.
+        if (_nextScope!=null && _nextScope==_handler)
+            _nextScope.doHandle(target,baseRequest,request, response);
+        else if (_handler!=null)
+            _handler.handle(target,baseRequest, request, response);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected boolean never()
+    {
+        return false;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/src/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
new file mode 100644
index 0000000..a8669e4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/ShutdownHandler.java
@@ -0,0 +1,170 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
+ * made.
+ *
+ * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
+ *
+ * Usage:
+ *
+ * <pre>
+    Server server = new Server(8080);
+    HandlerList handlers = new HandlerList();
+    handlers.setHandlers(new Handler[]
+    { someOtherHandler, new ShutdownHandler(server,&quot;secret password&quot;) });
+    server.setHandler(handlers);
+    server.start();
+   </pre>
+ * 
+   <pre>
+   public static void attemptShutdown(int port, String shutdownCookie) {
+        try {
+            URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
+            HttpURLConnection connection = (HttpURLConnection)url.openConnection();
+            connection.setRequestMethod("POST");
+            connection.getResponseCode();
+            logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
+        } catch (SocketException e) {
+            logger.debug("Not running");
+            // Okay - the server is not running
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+  </pre>
+ */
+public class ShutdownHandler extends AbstractHandler
+{
+    private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
+
+    private final String _shutdownToken;
+
+    private final Server _server;
+
+    private boolean _exitJvm = false;
+  
+    
+
+    /**
+     * Creates a listener that lets the server be shut down remotely (but only from localhost).
+     *
+     * @param server
+     *            the Jetty instance that should be shut down
+     * @param shutdownToken
+     *            a secret password to avoid unauthorized shutdown attempts
+     */
+    public ShutdownHandler(Server server, String shutdownToken)
+    {
+        this._server = server;
+        this._shutdownToken = shutdownToken;
+    }
+
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (!target.equals("/shutdown"))
+        {
+            return;
+        }
+
+        if (!request.getMethod().equals("POST"))
+        {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        if (!hasCorrectSecurityToken(request))
+        {
+            LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request));
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+        if (!requestFromLocalhost(request))
+        {
+            LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request));
+            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+            return;
+        }
+
+        LOG.info("Shutting down by request from " + getRemoteAddr(request));
+        
+        new Thread()
+        {
+            public void run ()
+            {
+                try
+                {
+                    shutdownServer();
+                }
+                catch (InterruptedException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException("Shutting down server",e);
+                }
+            }
+        }.start();
+    }
+
+    private boolean requestFromLocalhost(HttpServletRequest request)
+    {
+        return "127.0.0.1".equals(getRemoteAddr(request));
+    }
+
+    protected String getRemoteAddr(HttpServletRequest request)
+    {
+        return request.getRemoteAddr();
+    }
+
+    private boolean hasCorrectSecurityToken(HttpServletRequest request)
+    {
+        return _shutdownToken.equals(request.getParameter("token"));
+    }
+
+    private void shutdownServer() throws Exception
+    {
+        _server.stop();
+        
+        if (_exitJvm)
+        {
+            System.exit(0);
+        }
+    }
+
+    public void setExitJvm(boolean exitJvm)
+    {
+        this._exitJvm = exitJvm;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/src/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
new file mode 100644
index 0000000..6949dbd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -0,0 +1,474 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+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.server.AsyncContinuation;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+public class StatisticsHandler extends HandlerWrapper
+{
+    private final AtomicLong _statsStartedAt = new AtomicLong();
+    
+    private final CounterStatistic _requestStats = new CounterStatistic();
+    private final SampleStatistic _requestTimeStats = new SampleStatistic();
+    private final CounterStatistic _dispatchedStats = new CounterStatistic();
+    private final SampleStatistic _dispatchedTimeStats = new SampleStatistic();
+    private final CounterStatistic _suspendStats = new CounterStatistic();
+
+    private final AtomicInteger _resumes = new AtomicInteger();
+    private final AtomicInteger _expires = new AtomicInteger();
+    
+    private final AtomicInteger _responses1xx = new AtomicInteger();
+    private final AtomicInteger _responses2xx = new AtomicInteger();
+    private final AtomicInteger _responses3xx = new AtomicInteger();
+    private final AtomicInteger _responses4xx = new AtomicInteger();
+    private final AtomicInteger _responses5xx = new AtomicInteger();
+    private final AtomicLong _responsesTotalBytes = new AtomicLong();
+
+    private final ContinuationListener _onCompletion = new ContinuationListener()
+    {
+        public void onComplete(Continuation continuation)
+        {
+            final Request request = ((AsyncContinuation)continuation).getBaseRequest();
+            final long elapsed = System.currentTimeMillis()-request.getTimeStamp();
+            
+            _requestStats.decrement();
+            _requestTimeStats.set(elapsed);
+            
+            updateResponse(request);
+            
+            if (!continuation.isResumed())
+                _suspendStats.decrement();
+        }
+
+        public void onTimeout(Continuation continuation)
+        {
+            _expires.incrementAndGet();
+        }
+    };
+    
+    /**
+     * Resets the current request statistics.
+     */
+    public void statsReset()
+    {
+        _statsStartedAt.set(System.currentTimeMillis());
+        
+        _requestStats.reset();
+        _requestTimeStats.reset();
+        _dispatchedStats.reset();
+        _dispatchedTimeStats.reset();
+        _suspendStats.reset();
+
+        _resumes.set(0);
+        _expires.set(0);
+        _responses1xx.set(0);
+        _responses2xx.set(0);
+        _responses3xx.set(0);
+        _responses4xx.set(0);
+        _responses5xx.set(0);
+        _responsesTotalBytes.set(0L);
+    }
+
+    @Override
+    public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
+    {
+        _dispatchedStats.increment();
+
+        final long start;
+        AsyncContinuation continuation = request.getAsyncContinuation();
+        if (continuation.isInitial())
+        {
+            // new request
+            _requestStats.increment();
+            start = request.getTimeStamp();
+        }
+        else
+        {
+            // resumed request
+            start = System.currentTimeMillis();
+            _suspendStats.decrement();
+            if (continuation.isResumed())
+                _resumes.incrementAndGet();
+        }
+
+        try
+        {
+            super.handle(path, request, httpRequest, httpResponse);
+        }
+        finally
+        {
+            final long now = System.currentTimeMillis();
+            final long dispatched=now-start;
+            
+            _dispatchedStats.decrement();
+            _dispatchedTimeStats.set(dispatched);
+            
+            if (continuation.isSuspended())
+            {
+                if (continuation.isInitial())
+                    continuation.addContinuationListener(_onCompletion);
+                _suspendStats.increment();
+            }
+            else if (continuation.isInitial())
+            {
+                _requestStats.decrement();
+                _requestTimeStats.set(dispatched);
+                updateResponse(request);
+            }
+            // else onCompletion will handle it.
+        }
+    }
+
+    private void updateResponse(Request request)
+    {
+        Response response = request.getResponse();
+        switch (response.getStatus() / 100)
+        {
+            case 1:
+                _responses1xx.incrementAndGet();
+                break;
+            case 2:
+                _responses2xx.incrementAndGet();
+                break;
+            case 3:
+                _responses3xx.incrementAndGet();
+                break;
+            case 4:
+                _responses4xx.incrementAndGet();
+                break;
+            case 5:
+                _responses5xx.incrementAndGet();
+                break;
+            default:
+                break;
+        }
+        _responsesTotalBytes.addAndGet(response.getContentCount());
+    }
+
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        statsReset();
+    }
+
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active requests
+     * @see #getResumes()
+     */
+    public int getRequests()
+    {
+        return (int)_requestStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently active.
+     * since {@link #statsReset()} was last called.
+     */
+    public int getRequestsActive()
+    {
+        return (int)_requestStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of active requests
+     * since {@link #statsReset()} was last called.
+     */
+    public int getRequestsActiveMax()
+    {
+        return (int)_requestStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     */
+    public long getRequestTimeMax()
+    {
+        return _requestTimeStats.getMax();
+    }
+
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    public long getRequestTimeTotal()
+    {
+        return _requestTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    public double getRequestTimeMean()
+    {
+        return _requestTimeStats.getMean();
+    }
+
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    public double getRequestTimeStdDev()
+    {
+        return _requestTimeStats.getStdDev();
+    }
+
+    /**
+     * @return the number of dispatches seen by this handler
+     * since {@link #statsReset()} was last called, excluding
+     * active dispatches
+     */
+    public int getDispatched()
+    {
+        return (int)_dispatchedStats.getTotal();
+    }
+
+    /**
+     * @return the number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    public int getDispatchedActive()
+    {
+        return (int)_dispatchedStats.getCurrent();
+    }
+
+    /**
+     * @return the max number of dispatches currently in this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     */
+    public int getDispatchedActiveMax()
+    {
+        return (int)_dispatchedStats.getMax();
+    }
+
+    /**
+     * @return the maximum time (in milliseconds) of request dispatch
+     * since {@link #statsReset()} was last called.
+     */
+    public long getDispatchedTimeMax()
+    {
+        return _dispatchedTimeStats.getMax();
+    }
+    
+    /**
+     * @return the total time (in milliseconds) of requests handling
+     * since {@link #statsReset()} was last called.
+     */
+    public long getDispatchedTimeTotal()
+    {
+        return _dispatchedTimeStats.getTotal();
+    }
+
+    /**
+     * @return the mean time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    public double getDispatchedTimeMean()
+    {
+        return _dispatchedTimeStats.getMean();
+    }
+    
+    /**
+     * @return the standard deviation of time (in milliseconds) of request handling
+     * since {@link #statsReset()} was last called.
+     * @see #getRequestTimeTotal()
+     * @see #getRequests()
+     */
+    public double getDispatchedTimeStdDev()
+    {
+        return _dispatchedTimeStats.getStdDev();
+    }
+    
+    /**
+     * @return the number of requests handled by this handler
+     * since {@link #statsReset()} was last called, including
+     * resumed requests
+     * @see #getResumes()
+     */
+    public int getSuspends()
+    {
+        return (int)_suspendStats.getTotal();
+    }
+
+    /**
+     * @return the number of requests currently suspended.
+     * since {@link #statsReset()} was last called.
+     */
+    public int getSuspendsActive()
+    {
+        return (int)_suspendStats.getCurrent();
+    }
+
+    /**
+     * @return the maximum number of current suspended requests
+     * since {@link #statsReset()} was last called.
+     */
+    public int getSuspendsActiveMax()
+    {
+        return (int)_suspendStats.getMax();
+    }
+    
+    /**
+     * @return the number of requests that have been resumed
+     * @see #getExpires()
+     */
+    public int getResumes()
+    {
+        return _resumes.get();
+    }
+
+    /**
+     * @return the number of requests that expired while suspended.
+     * @see #getResumes()
+     */
+    public int getExpires()
+    {
+        return _expires.get();
+    }
+
+    /**
+     * @return the number of responses with a 1xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    public int getResponses1xx()
+    {
+        return _responses1xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 2xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    public int getResponses2xx()
+    {
+        return _responses2xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 3xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    public int getResponses3xx()
+    {
+        return _responses3xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 4xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    public int getResponses4xx()
+    {
+        return _responses4xx.get();
+    }
+
+    /**
+     * @return the number of responses with a 5xx status returned by this context
+     * since {@link #statsReset()} was last called.
+     */
+    public int getResponses5xx()
+    {
+        return _responses5xx.get();
+    }
+
+    /**
+     * @return the milliseconds since the statistics were started with {@link #statsReset()}.
+     */
+    public long getStatsOnMs()
+    {
+        return System.currentTimeMillis() - _statsStartedAt.get();
+    }
+    
+    /**
+     * @return the total bytes of content sent in responses
+     */
+    public long getResponsesBytesTotal()
+    {
+        return _responsesTotalBytes.get();
+    }
+    
+    public String toStatsHTML()
+    {   
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<h1>Statistics:</h1>\n");
+        sb.append("Statistics gathering started ").append(getStatsOnMs()).append("ms ago").append("<br />\n");
+
+        sb.append("<h2>Requests:</h2>\n");
+        sb.append("Total requests: ").append(getRequests()).append("<br />\n");
+        sb.append("Active requests: ").append(getRequestsActive()).append("<br />\n");
+        sb.append("Max active requests: ").append(getRequestsActiveMax()).append("<br />\n");
+        sb.append("Total requests time: ").append(getRequestTimeTotal()).append("<br />\n");
+        sb.append("Mean request time: ").append(getRequestTimeMean()).append("<br />\n");
+        sb.append("Max request time: ").append(getRequestTimeMax()).append("<br />\n");
+        sb.append("Request time standard deviation: ").append(getRequestTimeStdDev()).append("<br />\n");
+        
+
+        sb.append("<h2>Dispatches:</h2>\n");
+        sb.append("Total dispatched: ").append(getDispatched()).append("<br />\n");
+        sb.append("Active dispatched: ").append(getDispatchedActive()).append("<br />\n");
+        sb.append("Max active dispatched: ").append(getDispatchedActiveMax()).append("<br />\n");
+        sb.append("Total dispatched time: ").append(getDispatchedTimeTotal()).append("<br />\n");
+        sb.append("Mean dispatched time: ").append(getDispatchedTimeMean()).append("<br />\n");
+        sb.append("Max dispatched time: ").append(getDispatchedTimeMax()).append("<br />\n");
+        sb.append("Dispatched time standard deviation: ").append(getDispatchedTimeStdDev()).append("<br />\n");
+
+
+        sb.append("Total requests suspended: ").append(getSuspends()).append("<br />\n");
+        sb.append("Total requests expired: ").append(getExpires()).append("<br />\n");
+        sb.append("Total requests resumed: ").append(getResumes()).append("<br />\n");
+        
+        sb.append("<h2>Responses:</h2>\n");
+        sb.append("1xx responses: ").append(getResponses1xx()).append("<br />\n");
+        sb.append("2xx responses: ").append(getResponses2xx()).append("<br />\n");
+        sb.append("3xx responses: ").append(getResponses3xx()).append("<br />\n");
+        sb.append("4xx responses: ").append(getResponses4xx()).append("<br />\n");
+        sb.append("5xx responses: ").append(getResponses5xx()).append("<br />\n");
+        sb.append("Bytes sent total: ").append(getResponsesBytesTotal()).append("<br />\n");
+
+        return sb.toString();
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java b/src/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java
new file mode 100644
index 0000000..e43af98
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java
@@ -0,0 +1,121 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler.jmx;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class AbstractHandlerMBean extends ObjectMBean
+{
+    private static final Logger LOG = Log.getLogger(AbstractHandlerMBean.class);
+
+    /* ------------------------------------------------------------ */
+    public AbstractHandlerMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getObjectContextBasis()
+    {
+        if (_managed != null )
+        {
+            String basis = null;
+            if (_managed instanceof ContextHandler)
+            {
+                return null;
+            }
+            else if (_managed instanceof AbstractHandler)
+            {
+                AbstractHandler handler = (AbstractHandler)_managed;
+                Server server = handler.getServer();
+                if (server != null)
+                {
+                    ContextHandler context = 
+                        AbstractHandlerContainer.findContainerOf(server,
+                                ContextHandler.class, handler);
+                    
+                    if (context != null)
+                        basis = getContextName(context);
+                }
+            }
+            if (basis != null)
+                return basis;
+        }
+        return super.getObjectContextBasis();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getObjectNameBasis()
+    {
+        if (_managed != null )
+        {
+            String name = null;
+            if (_managed instanceof ContextHandler)
+            {
+                ContextHandler context = (ContextHandler)_managed;
+                name = getContextName(context);
+            }
+            
+            if (name != null)
+                return name;
+        }
+        
+        return super.getObjectNameBasis();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected String getContextName(ContextHandler context)
+    {
+        String name = null;
+        
+        if (context.getContextPath()!=null && context.getContextPath().length()>0)
+        {
+            int idx = context.getContextPath().lastIndexOf('/');
+            name = idx < 0 ? context.getContextPath() : context.getContextPath().substring(++idx);
+            if (name==null || name.length()==0)
+                name= "ROOT";
+        }
+        
+        if (name==null && context.getBaseResource()!=null)
+        {
+            try
+            {
+                if (context.getBaseResource().getFile()!=null)
+                    name = context.getBaseResource().getFile().getName();
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+                name=context.getBaseResource().getName();
+            }
+        }
+        
+        return name;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java b/src/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java
new file mode 100644
index 0000000..3848e1d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java
@@ -0,0 +1,66 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.handler.jmx;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+
+public class ContextHandlerMBean extends AbstractHandlerMBean
+{
+    public ContextHandlerMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    public Map getContextAttributes()
+    {
+        Map map = new HashMap();
+        Attributes attrs = ((ContextHandler)_managed).getAttributes();
+        Enumeration en = attrs.getAttributeNames();
+        while (en.hasMoreElements())
+        {
+            String name = (String)en.nextElement();
+            Object value = attrs.getAttribute(name);
+            map.put(name,value);
+        }
+        return map;
+    }
+    
+    public void setContextAttribute(String name, Object value)
+    {
+        Attributes attrs = ((ContextHandler)_managed).getAttributes();
+        attrs.setAttribute(name,value);
+    }
+    
+    public void setContextAttribute(String name, String value)
+    {
+        Attributes attrs = ((ContextHandler)_managed).getAttributes();
+        attrs.setAttribute(name,value);
+    }
+    
+    public void removeContextAttribute(String name)
+    {
+        Attributes attrs = ((ContextHandler)_managed).getAttributes();
+        attrs.removeAttribute(name);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/jmx/ServerMBean.java b/src/java/org/eclipse/jetty/server/jmx/ServerMBean.java
new file mode 100644
index 0000000..c0fa28f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/jmx/ServerMBean.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ *
+ */
+public class ServerMBean extends ObjectMBean
+{
+    private final long startupTime;
+    private final Server server;
+
+    public ServerMBean(Object managedObject)
+    {
+        super(managedObject);
+        startupTime = System.currentTimeMillis();
+        server = (Server)managedObject;
+    }
+
+    public Handler[] getContexts()
+    {
+        return server.getChildHandlersByClass(ContextHandler.class);
+    }
+
+    public long getStartupTime()
+    {
+        return startupTime;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java b/src/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
new file mode 100644
index 0000000..ecea8af
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/AbstractNIOConnector.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+/**
+ *
+ */
+package org.eclipse.jetty.server.nio;
+
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.server.AbstractConnector;
+
+public abstract class AbstractNIOConnector extends AbstractConnector implements NIOConnector
+{
+    public AbstractNIOConnector()
+    {
+        _buffers.setRequestBufferType(Type.DIRECT);
+        _buffers.setRequestHeaderType(Type.INDIRECT);
+        _buffers.setResponseBufferType(Type.DIRECT);
+        _buffers.setResponseHeaderType(Type.INDIRECT);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public boolean getUseDirectBuffers()
+    {
+        return getRequestBufferType()==Type.DIRECT;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @param direct If True (the default), the connector can use NIO direct buffers.
+     * Some JVMs have memory management issues (bugs) with direct buffers.
+     */
+    public void setUseDirectBuffers(boolean direct)
+    {
+        _buffers.setRequestBufferType(direct?Type.DIRECT:Type.INDIRECT);
+        _buffers.setResponseBufferType(direct?Type.DIRECT:Type.INDIRECT);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/src/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
new file mode 100644
index 0000000..b8f5c84
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
@@ -0,0 +1,366 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.Set;
+
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.nio.ChannelEndPoint;
+import org.eclipse.jetty.server.BlockingHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.ConcurrentHashSet;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------------------------- */
+/**  Blocking NIO connector.
+ * This connector uses efficient NIO buffers with a traditional blocking thread model.
+ * Direct NIO buffers are used and a thread is allocated per connections.
+ *
+ * This connector is best used when there are a few very active connections.
+ *
+ * @org.apache.xbean.XBean element="blockingNioConnector" description="Creates a blocking NIO based socket connector"
+ *
+ *
+ *
+ */
+public class BlockingChannelConnector extends AbstractNIOConnector
+{
+    private static final Logger LOG = Log.getLogger(BlockingChannelConnector.class);
+
+    private transient ServerSocketChannel _acceptChannel;
+    private final Set<BlockingChannelEndPoint> _endpoints = new ConcurrentHashSet<BlockingChannelEndPoint>();
+
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     *
+     */
+    public BlockingChannelConnector()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getConnection()
+    {
+        return _acceptChannel;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.AbstractConnector#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        getThreadPool().dispatch(new Runnable()
+        {
+
+            public void run()
+            {
+                while (isRunning())
+                {
+                    try
+                    {
+                        Thread.sleep(400);
+                        long now=System.currentTimeMillis();
+                        for (BlockingChannelEndPoint endp : _endpoints)
+                        {
+                            endp.checkIdleTimestamp(now);
+                        }
+                    }
+                    catch(InterruptedException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch(Exception e)
+                    {
+                        LOG.warn(e);
+                    }
+                }
+            }
+
+        });
+
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void open() throws IOException
+    {
+        // Create a new server socket and set to non blocking mode
+        _acceptChannel= ServerSocketChannel.open();
+        _acceptChannel.configureBlocking(true);
+
+        // Bind the server socket to the local host and port
+        InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
+        _acceptChannel.socket().bind(addr,getAcceptQueueSize());
+    }
+
+    /* ------------------------------------------------------------ */
+    public void close() throws IOException
+    {
+        if (_acceptChannel != null)
+            _acceptChannel.close();
+        _acceptChannel=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void accept(int acceptorID)
+    	throws IOException, InterruptedException
+    {
+        SocketChannel channel = _acceptChannel.accept();
+        channel.configureBlocking(true);
+        Socket socket=channel.socket();
+        configure(socket);
+
+        BlockingChannelEndPoint connection=new BlockingChannelEndPoint(channel);
+        connection.dispatch();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void customize(EndPoint endpoint, Request request)
+        throws IOException
+    {
+        super.customize(endpoint, request);
+        endpoint.setMaxIdleTime(_maxIdleTime);
+        configure(((SocketChannel)endpoint.getTransport()).socket());
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    public int getLocalPort()
+    {
+        if (_acceptChannel==null || !_acceptChannel.isOpen())
+            return -1;
+        return _acceptChannel.socket().getLocalPort();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    private class BlockingChannelEndPoint extends ChannelEndPoint implements Runnable, ConnectedEndPoint
+    {
+        private Connection _connection;
+        private int _timeout;
+        private volatile long _idleTimestamp;
+
+        BlockingChannelEndPoint(ByteChannel channel)
+            throws IOException
+        {
+            super(channel,BlockingChannelConnector.this._maxIdleTime);
+            _connection = new BlockingHttpConnection(BlockingChannelConnector.this,this,getServer());
+        }
+
+        /* ------------------------------------------------------------ */
+        /** Get the connection.
+         * @return the connection
+         */
+        public Connection getConnection()
+        {
+            return _connection;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setConnection(Connection connection)
+        {
+            _connection=connection;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void checkIdleTimestamp(long now)
+        {
+            if (_idleTimestamp!=0 && _timeout>0 && now>(_idleTimestamp+_timeout))
+            {
+                idleExpired();
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        protected void idleExpired()
+        {
+            try
+            {
+                super.close();
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        void dispatch() throws IOException
+        {
+            if (!getThreadPool().dispatch(this))
+            {
+                LOG.warn("dispatch failed for  {}",_connection);
+                super.close();
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#fill(org.eclipse.jetty.io.Buffer)
+         */
+        @Override
+        public int fill(Buffer buffer) throws IOException
+        {
+            _idleTimestamp=System.currentTimeMillis();
+            return super.fill(buffer);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer)
+         */
+        @Override
+        public int flush(Buffer buffer) throws IOException
+        {
+            _idleTimestamp=System.currentTimeMillis();
+            return super.flush(buffer);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @see org.eclipse.jetty.io.nio.ChannelEndPoint#flush(org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer, org.eclipse.jetty.io.Buffer)
+         */
+        @Override
+        public int flush(Buffer header, Buffer buffer, Buffer trailer) throws IOException
+        {
+            _idleTimestamp=System.currentTimeMillis();
+            return super.flush(header,buffer,trailer);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void run()
+        {
+            try
+            {
+                _timeout=getMaxIdleTime();
+                connectionOpened(_connection);
+                _endpoints.add(this);
+
+                while (isOpen())
+                {
+                    _idleTimestamp=System.currentTimeMillis();
+                    if (_connection.isIdle())
+                    {
+                        if (getServer().getThreadPool().isLowOnThreads())
+                        {
+                            int lrmit = getLowResourcesMaxIdleTime();
+                            if (lrmit>=0 && _timeout!= lrmit)
+                            {
+                                _timeout=lrmit;
+                            }
+                        }
+                    }
+                    else
+                    {
+                        if (_timeout!=getMaxIdleTime())
+                        {
+                            _timeout=getMaxIdleTime();
+                        }
+                    }
+
+                    _connection = _connection.handle();
+
+                }
+            }
+            catch (EofException e)
+            {
+                LOG.debug("EOF", e);
+                try{BlockingChannelEndPoint.this.close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch (HttpException e)
+            {
+                LOG.debug("BAD", e);
+                try{super.close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch(Throwable e)
+            {
+                LOG.warn("handle failed",e);
+                try{super.close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            finally
+            {
+                connectionClosed(_connection);
+                _endpoints.remove(this);
+
+                // wait for client to close, but if not, close ourselves.
+                try
+                {
+                    if (!_socket.isClosed())
+                    {
+                        long timestamp=System.currentTimeMillis();
+                        int max_idle=getMaxIdleTime();
+
+                        _socket.setSoTimeout(getMaxIdleTime());
+                        int c=0;
+                        do
+                        {
+                            c = _socket.getInputStream().read();
+                        }
+                        while (c>=0 && (System.currentTimeMillis()-timestamp)<max_idle);
+                        if (!_socket.isClosed())
+                            _socket.close();
+                    }
+                }
+                catch(IOException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}",
+                    hashCode(),
+                    _socket.getRemoteSocketAddress(),
+                    _socket.getLocalSocketAddress(),
+                    isOpen(),
+                    isInputShutdown(),
+                    isOutputShutdown(),
+                    _connection);
+        }
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java b/src/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
new file mode 100644
index 0000000..c7cdc1d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/InheritedChannelConnector.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.io.IOException;
+import java.nio.channels.Channel;
+import java.nio.channels.ServerSocketChannel;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An implementation of the SelectChannelConnector which first tries to  
+ * inherit from a channel provided by the system. If there is no inherited
+ * channel available, or if the inherited channel provided not usable, then 
+ * it will fall back upon normal ServerSocketChannel creation.
+ * <p> 
+ * Note that System.inheritedChannel() is only available from Java 1.5 onwards.
+ * Trying to use this class under Java 1.4 will be the same as using a normal
+ * SelectChannelConnector. 
+ * <p> 
+ * Use it with xinetd/inetd, to launch an instance of Jetty on demand. The port
+ * used to access pages on the Jetty instance is the same as the port used to
+ * launch Jetty. 
+ * 
+ * @author athena
+ */
+public class InheritedChannelConnector extends SelectChannelConnector
+{
+    private static final Logger LOG = Log.getLogger(InheritedChannelConnector.class);
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void open() throws IOException
+    {
+        synchronized(this)
+        {
+            try 
+            {
+                Channel channel = System.inheritedChannel();
+                if ( channel instanceof ServerSocketChannel )
+                    _acceptChannel = (ServerSocketChannel)channel;
+                else
+                    LOG.warn("Unable to use System.inheritedChannel() [" +channel+ "]. Trying a new ServerSocketChannel at " + getHost() + ":" + getPort());
+                
+                if ( _acceptChannel != null )
+                    _acceptChannel.configureBlocking(true);
+            }
+            catch(NoSuchMethodError e)
+            {
+                LOG.warn("Need at least Java 5 to use socket inherited from xinetd/inetd.");
+            }
+
+            if (_acceptChannel == null)
+                super.open();
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/NIOConnector.java b/src/java/org/eclipse/jetty/server/nio/NIOConnector.java
new file mode 100644
index 0000000..b80b6a8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/NIOConnector.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio; 
+
+/** 
+ * NIOConnector.
+ * A marker interface that indicates that NIOBuffers can be handled (efficiently) by this Connector.
+ * 
+ * 
+ * 
+ */
+public interface NIOConnector
+{
+    boolean getUseDirectBuffers();
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java b/src/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
new file mode 100644
index 0000000..17be3fb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/NetworkTrafficSelectChannelConnector.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.ConcurrentModificationException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.nio.NetworkTrafficSelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+
+/**
+ * <p>A specialized version of {@link SelectChannelConnector} that supports {@link NetworkTrafficListener}s.</p>
+ * <p>{@link NetworkTrafficListener}s can be added and removed dynamically before and after this connector has
+ * been started without causing {@link ConcurrentModificationException}s.</p>
+ */
+public class NetworkTrafficSelectChannelConnector extends SelectChannelConnector
+{
+    private final List<NetworkTrafficListener> listeners = new CopyOnWriteArrayList<NetworkTrafficListener>();
+
+    /**
+     * @param listener the listener to add
+     */
+    public void addNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    /**
+     * @param listener the listener to remove
+     */
+    public void removeNetworkTrafficListener(NetworkTrafficListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    @Override
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectorManager.SelectSet selectSet, SelectionKey key) throws IOException
+    {
+        NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, _maxIdleTime, listeners);
+        endPoint.setConnection(selectSet.getManager().newConnection(channel,endPoint, key.attachment()));
+        endPoint.notifyOpened();
+        return endPoint;
+    }
+
+    @Override
+    protected void endPointClosed(SelectChannelEndPoint endpoint)
+    {
+        super.endPointClosed(endpoint);
+        ((NetworkTrafficSelectChannelEndPoint)endpoint).notifyClosed();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/src/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
new file mode 100644
index 0000000..8ed6249
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
@@ -0,0 +1,334 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.nio;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.nio.SelectorManager.SelectSet;
+import org.eclipse.jetty.server.AsyncHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Selecting NIO connector.
+ * <p>
+ * This connector uses efficient NIO buffers with a non blocking threading model. Direct NIO buffers
+ * are used and threads are only allocated to connections with requests. Synchronization is used to
+ * simulate blocking for the servlet API, and any unflushed content at the end of request handling
+ * is written asynchronously.
+ * </p>
+ * <p>
+ * This connector is best used when there are a many connections that have idle periods.
+ * </p>
+ * <p>
+ * When used with {@link org.eclipse.jetty.continuation.Continuation}, threadless waits are supported.
+ * If a filter or servlet returns after calling {@link Continuation#suspend()} or when a
+ * runtime exception is thrown from a call to {@link Continuation#undispatch()}, Jetty will
+ * will not send a response to the client. Instead the thread is released and the Continuation is
+ * placed on the timer queue. If the Continuation timeout expires, or it's
+ * resume method is called, then the request is again allocated a thread and the request is retried.
+ * The limitation of this approach is that request content is not available on the retried request,
+ * thus if possible it should be read after the continuation or saved as a request attribute or as the
+ * associated object of the Continuation instance.
+ * </p>
+ *
+ * @org.apache.xbean.XBean element="nioConnector" description="Creates an NIO based socket connector"
+ */
+public class SelectChannelConnector extends AbstractNIOConnector
+{
+    protected ServerSocketChannel _acceptChannel;
+    private int _lowResourcesConnections;
+    private int _lowResourcesMaxIdleTime;
+    private int _localPort=-1;
+
+    private final SelectorManager _manager = new ConnectorSelectorManager();
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Constructor.
+     *
+     */
+    public SelectChannelConnector()
+    {
+        _manager.setMaxIdleTime(getMaxIdleTime());
+        addBean(_manager,true);
+        setAcceptors(Math.max(1,(Runtime.getRuntime().availableProcessors()+3)/4));
+    }
+    
+    @Override
+    public void setThreadPool(ThreadPool pool)
+    {
+        super.setThreadPool(pool);
+        // preserve start order
+        removeBean(_manager);
+        addBean(_manager,true);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void accept(int acceptorID) throws IOException
+    {
+        ServerSocketChannel server;
+        synchronized(this)
+        {
+            server = _acceptChannel;
+        }
+
+        if (server!=null && server.isOpen() && _manager.isStarted())
+        {
+            SocketChannel channel = server.accept();
+            channel.configureBlocking(false);
+            Socket socket = channel.socket();
+            configure(socket);
+            _manager.register(channel);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void close() throws IOException
+    {
+        synchronized(this)
+        {
+            if (_acceptChannel != null)
+            {
+                removeBean(_acceptChannel);
+                if (_acceptChannel.isOpen())
+                    _acceptChannel.close();
+            }
+            _acceptChannel = null;
+            _localPort=-2;
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void customize(EndPoint endpoint, Request request) throws IOException
+    {
+        request.setTimeStamp(System.currentTimeMillis());
+        endpoint.setMaxIdleTime(_maxIdleTime);
+        super.customize(endpoint, request);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void persist(EndPoint endpoint) throws IOException
+    {
+        AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint);
+        aEndp.setCheckForIdle(true);
+        super.persist(endpoint);
+    }
+
+    /* ------------------------------------------------------------ */
+    public SelectorManager getSelectorManager()
+    {
+        return _manager;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Object getConnection()
+    {
+        return _acceptChannel;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    public int getLocalPort()
+    {
+        synchronized(this)
+        {
+            return _localPort;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void open() throws IOException
+    {
+        synchronized(this)
+        {
+            if (_acceptChannel == null)
+            {
+                // Create a new server socket
+                _acceptChannel = ServerSocketChannel.open();
+                // Set to blocking mode
+                _acceptChannel.configureBlocking(true);
+
+                // Bind the server socket to the local host and port
+                _acceptChannel.socket().setReuseAddress(getReuseAddress());
+                InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
+                _acceptChannel.socket().bind(addr,getAcceptQueueSize());
+
+                _localPort=_acceptChannel.socket().getLocalPort();
+                if (_localPort<=0)
+                    throw new IOException("Server channel not bound");
+
+                addBean(_acceptChannel);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setMaxIdleTime(int maxIdleTime)
+    {
+        _manager.setMaxIdleTime(maxIdleTime);
+        super.setMaxIdleTime(maxIdleTime);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the lowResourcesConnections
+     */
+    public int getLowResourcesConnections()
+    {
+        return _lowResourcesConnections;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the number of connections, which if exceeded places this manager in low resources state.
+     * This is not an exact measure as the connection count is averaged over the select sets.
+     * @param lowResourcesConnections the number of connections
+     * @see #setLowResourcesMaxIdleTime(int)
+     */
+    public void setLowResourcesConnections(int lowResourcesConnections)
+    {
+        _lowResourcesConnections=lowResourcesConnections;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the lowResourcesMaxIdleTime
+     */
+    @Override
+    public int getLowResourcesMaxIdleTime()
+    {
+        return _lowResourcesMaxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the period in ms that a connection is allowed to be idle when this there are more
+     * than {@link #getLowResourcesConnections()} connections.  This allows the server to rapidly close idle connections
+     * in order to gracefully handle high load situations.
+     * @param lowResourcesMaxIdleTime the period in ms that a connection is allowed to be idle when resources are low.
+     * @see #setMaxIdleTime(int)
+     */
+    @Override
+    public void setLowResourcesMaxIdleTime(int lowResourcesMaxIdleTime)
+    {
+        _lowResourcesMaxIdleTime=lowResourcesMaxIdleTime;
+        super.setLowResourcesMaxIdleTime(lowResourcesMaxIdleTime);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.server.AbstractConnector#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _manager.setSelectSets(getAcceptors());
+        _manager.setMaxIdleTime(getMaxIdleTime());
+        _manager.setLowResourcesConnections(getLowResourcesConnections());
+        _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
+    {
+        SelectChannelEndPoint endp= new SelectChannelEndPoint(channel,selectSet,key, SelectChannelConnector.this._maxIdleTime);
+        endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
+        return endp;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected void endPointClosed(SelectChannelEndPoint endpoint)
+    {
+        connectionClosed(endpoint.getConnection());
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    protected AsyncConnection newConnection(SocketChannel channel,final AsyncEndPoint endpoint)
+    {
+        return new AsyncHttpConnection(SelectChannelConnector.this,endpoint,getServer());
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private final class ConnectorSelectorManager extends SelectorManager
+    {
+        @Override
+        public boolean dispatch(Runnable task)
+        {
+            ThreadPool pool=getThreadPool();
+            if (pool==null)
+                pool=getServer().getThreadPool();
+            return pool.dispatch(task);
+        }
+
+        @Override
+        protected void endPointClosed(final SelectChannelEndPoint endpoint)
+        {
+            SelectChannelConnector.this.endPointClosed(endpoint);
+        }
+
+        @Override
+        protected void endPointOpened(SelectChannelEndPoint endpoint)
+        {
+            // TODO handle max connections and low resources
+            connectionOpened(endpoint.getConnection());
+        }
+
+        @Override
+        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
+        {
+            connectionUpgraded(oldConnection,endpoint.getConnection());
+        }
+
+        @Override
+        public AsyncConnection newConnection(SocketChannel channel,AsyncEndPoint endpoint, Object attachment)
+        {
+            return SelectChannelConnector.this.newConnection(channel,endpoint);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
+        {
+            return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/session/AbstractSession.java b/src/java/org/eclipse/jetty/server/session/AbstractSession.java
new file mode 100644
index 0000000..1352fff
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/AbstractSession.java
@@ -0,0 +1,568 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ *
+ * <p>
+ * Implements {@link javax.servlet.http.HttpSession} from the <code>javax.servlet</code> package.
+ * </p>
+ * 
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSession implements AbstractSessionManager.SessionIf
+{
+    final static Logger LOG = SessionHandler.LOG;
+    
+    private final AbstractSessionManager _manager;
+    private final String _clusterId; // ID unique within cluster
+    private final String _nodeId;    // ID unique within node
+    private final Map<String,Object> _attributes=new HashMap<String, Object>();
+    private boolean _idChanged;
+    private final long _created;
+    private long _cookieSet;
+    private long _accessed;         // the time of the last access
+    private long _lastAccessed;     // the time of the last access excluding this one
+    private boolean _invalid;
+    private boolean _doInvalidate;
+    private long _maxIdleMs;
+    private boolean _newSession;
+    private int _requests;
+
+
+    
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, HttpServletRequest request)
+    {
+        _manager = abstractSessionManager;
+        
+        _newSession=true;
+        _created=System.currentTimeMillis();
+        _clusterId=_manager._sessionIdManager.newSessionId(request,_created);
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,request);
+        _accessed=_created;
+        _lastAccessed=_created;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session & id "+_nodeId+" "+_clusterId);
+    }
+
+    /* ------------------------------------------------------------- */
+    protected AbstractSession(AbstractSessionManager abstractSessionManager, long created, long accessed, String clusterId)
+    {
+        _manager = abstractSessionManager;
+        _created=created;
+        _clusterId=clusterId;
+        _nodeId=_manager._sessionIdManager.getNodeId(_clusterId,null);
+        _accessed=accessed;
+        _lastAccessed=accessed;
+        _requests=1;
+        _maxIdleMs=_manager._dftMaxIdleSecs>0?_manager._dftMaxIdleSecs*1000L:-1;
+        if (LOG.isDebugEnabled())
+            LOG.debug("new session "+_nodeId+" "+_clusterId);
+    }
+    
+    /* ------------------------------------------------------------- */
+    /**
+     * asserts that the session is valid
+     */
+    protected void checkValid() throws IllegalStateException
+    {
+        if (_invalid)
+            throw new IllegalStateException();
+    }
+    
+    /* ------------------------------------------------------------- */
+    public AbstractSession getSession()
+    {
+        return this;
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getAccessed()
+    {
+        synchronized (this)
+        {
+            return _accessed;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object getAttribute(String name)
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return _attributes.get(name);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getAttributes()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            return _attributes.size();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings({ "unchecked" })
+    public Enumeration<String> getAttributeNames()
+    {
+        synchronized (this)
+        {
+            checkValid();
+            List<String> names=_attributes==null?Collections.EMPTY_LIST:new ArrayList<String>(_attributes.keySet());
+            return Collections.enumeration(names);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> getNames()
+    {
+        synchronized (this)
+        { 
+            return new HashSet<String>(_attributes.keySet());
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getCookieSetTime()
+    {
+        return _cookieSet;
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getCreationTime() throws IllegalStateException
+    {
+        return _created;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getId() throws IllegalStateException
+    {
+        return _manager._nodeIdInSessionId?_nodeId:_clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getNodeId()
+    {
+        return _nodeId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public String getClusterId()
+    {
+        return _clusterId;
+    }
+
+    /* ------------------------------------------------------------- */
+    public long getLastAccessedTime() throws IllegalStateException
+    {
+        checkValid();
+        return _lastAccessed;
+    }
+    
+    /* ------------------------------------------------------------- */
+    public void setLastAccessedTime(long time)
+    {
+        _lastAccessed = time;
+    }
+
+    /* ------------------------------------------------------------- */
+    public int getMaxInactiveInterval()
+    {
+        checkValid();
+        return (int)(_maxIdleMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.http.HttpSession#getServletContext()
+     */
+    public ServletContext getServletContext()
+    {
+        return _manager._context;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Deprecated
+    public HttpSessionContext getSessionContext() throws IllegalStateException
+    {
+        checkValid();
+        return AbstractSessionManager.__nullSessionContext;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttribute}
+     */
+    @Deprecated
+    public Object getValue(String name) throws IllegalStateException
+    {
+        return getAttribute(name);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #getAttributeNames}
+     */
+    @Deprecated
+    public String[] getValueNames() throws IllegalStateException
+    {
+        synchronized(this)
+        {
+            checkValid();
+            if (_attributes==null)
+                return new String[0];
+            String[] a=new String[_attributes.size()];
+            return (String[])_attributes.keySet().toArray(a);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected  Map<String,Object> getAttributeMap ()
+    {
+        return _attributes;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void addAttributes(Map<String,Object> map)
+    {
+        _attributes.putAll(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean access(long time)
+    {
+        synchronized(this)
+        {
+            if (_invalid)
+                return false;
+            _newSession=false;
+            _lastAccessed=_accessed;
+            _accessed=time;
+
+            if (_maxIdleMs>0 && _lastAccessed>0 && _lastAccessed + _maxIdleMs < time) 
+            {
+                invalidate();
+                return false;
+            }
+            _requests++;
+            return true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void complete()
+    {
+        synchronized(this)
+        {
+            _requests--;
+            if (_doInvalidate && _requests<=0  )
+                doInvalidate();
+        }
+    }
+
+
+    /* ------------------------------------------------------------- */
+    protected void timeout() throws IllegalStateException
+    {
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+
+        // Notify listeners and unbind values
+        boolean do_invalidate=false;
+        synchronized (this)
+        {
+            if (!_invalid)
+            {
+                if (_requests<=0)
+                    do_invalidate=true;
+                else
+                    _doInvalidate=true;
+            }
+        }
+        if (do_invalidate)
+            doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    public void invalidate() throws IllegalStateException
+    {
+        // remove session from context and invalidate other sessions with same ID.
+        _manager.removeSession(this,true);
+        doInvalidate();
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void doInvalidate() throws IllegalStateException
+    {
+        try
+        {
+            LOG.debug("invalidate {}",_clusterId);
+            if (isValid())
+                clearAttributes();
+        }
+        finally
+        {
+            synchronized (this)
+            {
+                // mark as invalid
+                _invalid=true;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public void clearAttributes() 
+    {
+        while (_attributes!=null && _attributes.size()>0)
+        {
+            ArrayList<String> keys;
+            synchronized(this)
+            {
+                keys=new ArrayList<String>(_attributes.keySet());
+            }
+
+            Iterator<String> iter=keys.iterator();
+            while (iter.hasNext())
+            {
+                String key=(String)iter.next();
+
+                Object value;
+                synchronized(this)
+                {
+                    value=doPutOrRemove(key,null);
+                }
+                unbindValue(key,value);
+
+                _manager.doSessionAttributeListeners(this,key,value,null);
+            }
+        } 
+        if (_attributes!=null)
+            _attributes.clear();
+    }
+    
+    /* ------------------------------------------------------------- */
+    public boolean isIdChanged()
+    {
+        return _idChanged;
+    }
+
+    /* ------------------------------------------------------------- */
+    public boolean isNew() throws IllegalStateException
+    {
+        checkValid();
+        return _newSession;
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #setAttribute}
+     */
+    @Deprecated
+    public void putValue(java.lang.String name, java.lang.Object value) throws IllegalStateException
+    {
+        setAttribute(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeAttribute(String name)
+    {
+        setAttribute(name,null);
+    }
+
+    /* ------------------------------------------------------------- */
+    /**
+     * @deprecated As of Version 2.2, this method is replaced by
+     *             {@link #removeAttribute}
+     */
+    @Deprecated
+    public void removeValue(java.lang.String name) throws IllegalStateException
+    {
+        removeAttribute(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Object doPutOrRemove(String name, Object value)
+    {
+        return value==null?_attributes.remove(name):_attributes.put(name,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Object doGet(String name)
+    {
+        return _attributes.get(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setAttribute(String name, Object value)
+    {
+        Object old=null;
+        synchronized (this)
+        {
+            checkValid();
+            old=doPutOrRemove(name,value);
+        }
+        
+        if (value==null || !value.equals(old))
+        {
+            if (old!=null)
+                unbindValue(name,old);
+            if (value!=null)
+                bindValue(name,value);
+
+            _manager.doSessionAttributeListeners(this,name,old,value);
+            
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public void setIdChanged(boolean changed)
+    {
+        _idChanged=changed;
+    }
+
+    /* ------------------------------------------------------------- */
+    public void setMaxInactiveInterval(int secs)
+    {
+        _maxIdleMs=(long)secs*1000L;
+    }
+
+    /* ------------------------------------------------------------- */
+    @Override
+    public String toString()
+    {
+        return this.getClass().getName()+":"+getId()+"@"+hashCode();
+    }
+
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueBound() */
+    public void bindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueBound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isValid()
+    {
+        return !_invalid;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void cookieSet()
+    {
+        synchronized (this)
+        {
+            _cookieSet=_accessed;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRequests()
+    {
+        synchronized (this)
+        {
+            return _requests;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRequests(int requests)
+    {
+        synchronized (this)
+        {
+            _requests=requests;
+        }
+    }
+    
+    /* ------------------------------------------------------------- */
+    /** If value implements HttpSessionBindingListener, call valueUnbound() */
+    public void unbindValue(java.lang.String name, Object value)
+    {
+        if (value!=null&&value instanceof HttpSessionBindingListener)
+            ((HttpSessionBindingListener)value).valueUnbound(new HttpSessionBindingEvent(this,name));
+    }
+
+    /* ------------------------------------------------------------- */
+    public void willPassivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionWillPassivate(event);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------- */
+    public void didActivate()
+    {
+        synchronized(this)
+        {
+            HttpSessionEvent event = new HttpSessionEvent(this);
+            for (Iterator<Object> iter = _attributes.values().iterator(); iter.hasNext();)
+            {
+                Object value = iter.next();
+                if (value instanceof HttpSessionActivationListener)
+                {
+                    HttpSessionActivationListener listener = (HttpSessionActivationListener) value;
+                    listener.sessionDidActivate(event);
+                }
+            }
+        }
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java b/src/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
new file mode 100644
index 0000000..076d1cc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/AbstractSessionIdManager.java
@@ -0,0 +1,220 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public abstract class AbstractSessionIdManager extends AbstractLifeCycle implements SessionIdManager
+{
+    private static final Logger LOG = Log.getLogger(AbstractSessionIdManager.class);
+
+    private final static String __NEW_SESSION_ID="org.eclipse.jetty.server.newSessionId";  
+    
+    protected Random _random;
+    protected boolean _weakRandom;
+    protected String _workerName;
+    protected long _reseed=100000L;
+    
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public AbstractSessionIdManager(Random random)
+    {
+        _random=random;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the reseed probability
+     */
+    public long getReseed()
+    {
+        return _reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the reseed probability.
+     * @param reseed  If non zero then when a random long modulo the reseed value == 1, the {@link SecureRandom} will be reseeded.
+     */
+    public void setReseed(long reseed)
+    {
+        _reseed = reseed;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     * 
+     * @return String or null
+     */
+    public String getWorkerName()
+    {
+        return _workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the workname. If set, the workername is dot appended to the session
+     * ID and can be used to assist session affinity in a load balancer.
+     * 
+     * @param workerName
+     */
+    public void setWorkerName(String workerName)
+    {
+        if (workerName.contains("."))
+            throw new IllegalArgumentException("Name cannot contain '.'");
+        _workerName=workerName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Random getRandom()
+    {
+        return _random;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setRandom(Random random)
+    {
+        _random=random;
+        _weakRandom=false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * Create a new session id if necessary.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#newSessionId(javax.servlet.http.HttpServletRequest, long)
+     */
+    public String newSessionId(HttpServletRequest request, long created)
+    {
+        synchronized (this)
+        {
+            if (request!=null)
+            {
+                // A requested session ID can only be used if it is in use already.
+                String requested_id=request.getRequestedSessionId();
+                if (requested_id!=null)
+                {
+                    String cluster_id=getClusterId(requested_id);
+                    if (idInUse(cluster_id))
+                        return cluster_id;
+                }
+
+                // Else reuse any new session ID already defined for this request.
+                String new_id=(String)request.getAttribute(__NEW_SESSION_ID);
+                if (new_id!=null&&idInUse(new_id))
+                    return new_id;
+            }
+            
+            // pick a new unique ID!
+            String id=null;
+            while (id==null||id.length()==0||idInUse(id))
+            {
+                long r0=_weakRandom
+                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
+                :_random.nextLong();
+                if (r0<0)
+                    r0=-r0;
+
+		// random chance to reseed
+		if (_reseed>0 && (r0%_reseed)== 1L)
+		{
+		    LOG.debug("Reseeding {}",this);
+		    if (_random instanceof SecureRandom)
+		    {
+			SecureRandom secure = (SecureRandom)_random;
+			secure.setSeed(secure.generateSeed(8));
+		    }
+		    else
+		    {
+			_random.setSeed(_random.nextLong()^System.currentTimeMillis()^request.hashCode()^Runtime.getRuntime().freeMemory());
+		    }
+		}
+
+                long r1=_weakRandom
+                ?(hashCode()^Runtime.getRuntime().freeMemory()^_random.nextInt()^(((long)request.hashCode())<<32))
+                :_random.nextLong();
+                if (r1<0)
+                    r1=-r1;
+                id=Long.toString(r0,36)+Long.toString(r1,36);
+                
+                //add in the id of the node to ensure unique id across cluster
+                //NOTE this is different to the node suffix which denotes which node the request was received on
+                if (_workerName!=null)
+                    id=_workerName + id;
+            }
+
+            request.setAttribute(__NEW_SESSION_ID,id);
+            return id;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+       initRandom();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Set up a random number generator for the sessionids.
+     * 
+     * By preference, use a SecureRandom but allow to be injected.
+     */
+    public void initRandom ()
+    {
+        if (_random==null)
+        {
+            try
+            {
+                _random=new SecureRandom();
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Could not generate SecureRandom for session-id randomness",e);
+                _random=new Random();
+                _weakRandom=true;
+            }
+        }
+        else
+            _random.setSeed(_random.nextLong()^System.currentTimeMillis()^hashCode()^Runtime.getRuntime().freeMemory()); 
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/src/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
new file mode 100644
index 0000000..f38be8b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/AbstractSessionManager.java
@@ -0,0 +1,1026 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import static java.lang.Math.round;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.statistic.CounterStatistic;
+import org.eclipse.jetty.util.statistic.SampleStatistic;
+
+/* ------------------------------------------------------------ */
+/**
+ * An Abstract implementation of SessionManager. The partial implementation of
+ * SessionManager interface provides the majority of the handling required to
+ * implement a SessionManager. Concrete implementations of SessionManager based
+ * on AbstractSessionManager need only implement the newSession method to return
+ * a specialised version of the Session inner class that provides an attribute
+ * Map.
+ * <p>
+ */
+@SuppressWarnings("deprecation")
+public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager
+{
+    final static Logger __log = SessionHandler.LOG;
+
+    public Set<SessionTrackingMode> __defaultSessionTrackingModes =
+        Collections.unmodifiableSet(
+            new HashSet<SessionTrackingMode>(
+                    Arrays.asList(new SessionTrackingMode[]{SessionTrackingMode.COOKIE,SessionTrackingMode.URL})));
+        
+    public final static String SESSION_KNOWN_ONLY_TO_AUTHENTICATED="org.eclipse.jetty.security.sessionKnownOnlytoAuthenticated";
+    
+    /* ------------------------------------------------------------ */
+    public final static int __distantFuture=60*60*24*7*52*20;
+
+    static final HttpSessionContext __nullSessionContext=new HttpSessionContext()
+    {
+        public HttpSession getSession(String sessionId)
+        {
+            return null;
+        }
+        
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        public Enumeration getIds()
+        {
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        }
+    };
+    
+    private boolean _usingCookies=true;
+
+    /* ------------------------------------------------------------ */
+    // Setting of max inactive interval for new sessions
+    // -1 means no timeout
+    protected int _dftMaxIdleSecs=-1;
+    protected SessionHandler _sessionHandler;
+    protected boolean _httpOnly=false;
+    protected SessionIdManager _sessionIdManager;
+    protected boolean _secureCookies=false;
+    protected boolean _secureRequestOnly=true;
+
+    protected final List<HttpSessionAttributeListener> _sessionAttributeListeners = new CopyOnWriteArrayList<HttpSessionAttributeListener>();
+    protected final List<HttpSessionListener> _sessionListeners= new CopyOnWriteArrayList<HttpSessionListener>();
+
+    protected ClassLoader _loader;
+    protected ContextHandler.Context _context;
+    protected String _sessionCookie=__DefaultSessionCookie;
+    protected String _sessionIdPathParameterName = __DefaultSessionIdPathParameterName;
+    protected String _sessionIdPathParameterNamePrefix =";"+ _sessionIdPathParameterName +"=";
+    protected String _sessionDomain;
+    protected String _sessionPath;
+    protected int _maxCookieAge=-1;
+    protected int _refreshCookieAge;
+    protected boolean _nodeIdInSessionId;
+    protected boolean _checkingRemoteSessionIdEncoding;
+    protected String _sessionComment;
+
+    public Set<SessionTrackingMode> _sessionTrackingModes;
+
+    private boolean _usingURLs;
+    
+    protected final CounterStatistic _sessionsStats = new CounterStatistic();
+    protected final SampleStatistic _sessionTimeStats = new SampleStatistic();
+    
+    
+    /* ------------------------------------------------------------ */
+    public static HttpSession renewSession (HttpServletRequest request, HttpSession httpSession, boolean authenticated)
+    {
+        Map<String,Object> attributes = new HashMap<String, Object>();
+
+        for (Enumeration<String> e=httpSession.getAttributeNames();e.hasMoreElements();)
+        {
+            String name=e.nextElement();
+            attributes.put(name,httpSession.getAttribute(name));
+            httpSession.removeAttribute(name);
+        }
+
+        httpSession.invalidate();       
+        httpSession = request.getSession(true);
+        if (authenticated)
+            httpSession.setAttribute(SESSION_KNOWN_ONLY_TO_AUTHENTICATED, Boolean.TRUE);
+        for (Map.Entry<String, Object> entry: attributes.entrySet())
+            httpSession.setAttribute(entry.getKey(),entry.getValue());
+        return httpSession;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public AbstractSessionManager()
+    {
+        setSessionTrackingModes(__defaultSessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler.Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ContextHandler getContextHandler()
+    {
+        return _context.getContextHandler();
+    }
+    
+    public String getSessionPath()
+    {
+        return _sessionPath;
+    }
+
+    public int getMaxCookieAge()
+    {
+        return _maxCookieAge;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpCookie access(HttpSession session,boolean secure)
+    {
+        long now=System.currentTimeMillis();
+
+        AbstractSession s = ((SessionIf)session).getSession();
+
+       if (s.access(now))
+       {
+            // Do we need to refresh the cookie?
+            if (isUsingCookies() &&
+                (s.isIdChanged() ||
+                (getSessionCookieConfig().getMaxAge()>0 && getRefreshCookieAge()>0 && ((now-s.getCookieSetTime())/1000>getRefreshCookieAge()))
+                )
+               )
+            {
+                HttpCookie cookie=getSessionCookie(session,_context==null?"/":(_context.getContextPath()),secure);
+                s.cookieSet();
+                s.setIdChanged(false);
+                return cookie;
+            }
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.add((HttpSessionAttributeListener)listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.add((HttpSessionListener)listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearEventListeners()
+    {
+        _sessionAttributeListeners.clear();
+        _sessionListeners.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void complete(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        s.complete();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart() throws Exception
+    {
+        _context=ContextHandler.getCurrentContext();
+        _loader=Thread.currentThread().getContextClassLoader();
+
+        if (_sessionIdManager==null)
+        {
+            final Server server=getSessionHandler().getServer();
+            synchronized (server)
+            {
+                _sessionIdManager=server.getSessionIdManager();
+                if (_sessionIdManager==null)
+                {
+                    _sessionIdManager=new HashSessionIdManager();
+                    server.setSessionIdManager(_sessionIdManager);
+                }
+            }
+        }
+        if (!_sessionIdManager.isStarted())
+            _sessionIdManager.start();
+
+        // Look for a session cookie name
+        if (_context!=null)
+        {
+            String tmp=_context.getInitParameter(SessionManager.__SessionCookieProperty);
+            if (tmp!=null)
+                _sessionCookie=tmp;
+
+            tmp=_context.getInitParameter(SessionManager.__SessionIdPathParameterNameProperty);
+            if (tmp!=null)
+                setSessionIdPathParameterName(tmp);
+
+            // set up the max session cookie age if it isn't already
+            if (_maxCookieAge==-1)
+            {
+                tmp=_context.getInitParameter(SessionManager.__MaxAgeProperty);
+                if (tmp!=null)
+                    _maxCookieAge=Integer.parseInt(tmp.trim());
+            }
+
+            // set up the session domain if it isn't already
+            if (_sessionDomain==null)
+                _sessionDomain=_context.getInitParameter(SessionManager.__SessionDomainProperty);
+
+            // set up the sessionPath if it isn't already
+            if (_sessionPath==null)
+                _sessionPath=_context.getInitParameter(SessionManager.__SessionPathProperty);
+            
+            tmp=_context.getInitParameter(SessionManager.__CheckRemoteSessionEncoding);
+            if (tmp!=null)
+                _checkingRemoteSessionIdEncoding=Boolean.parseBoolean(tmp);
+        }
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop() throws Exception
+    {
+        super.doStop();
+
+        invalidateSessions();
+
+        _loader=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the httpOnly.
+     */
+    public boolean getHttpOnly()
+    {
+        return _httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    public HttpSession getHttpSession(String nodeId)
+    {
+        String cluster_id = getSessionIdManager().getClusterId(nodeId);
+
+        AbstractSession session = getSession(cluster_id);
+        if (session!=null && !session.getNodeId().equals(nodeId))
+            session.setIdChanged(true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the metaManager used for cross context session management
+     * @deprecated Use {@link #getSessionIdManager()}
+     */
+    public SessionIdManager getIdManager()
+    {
+        return getSessionIdManager();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the SessionIdManager used for cross context session management
+     */
+    public SessionIdManager getSessionIdManager()
+    {
+        return _sessionIdManager;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds
+     */
+    @Override
+    public int getMaxInactiveInterval()
+    {
+        return _dftMaxIdleSecs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see #getSessionsMax()
+     */
+    @Deprecated
+    public int getMaxSessions()
+    {
+        return getSessionsMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum number of sessions
+     */
+    public int getSessionsMax()
+    {
+        return (int)_sessionsStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total number of sessions
+     */
+    public int getSessionsTotal()
+    {
+        return (int)_sessionsStats.getTotal();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @deprecated use {@link #getSessionIdManager()}
+     */
+    @Deprecated
+    public SessionIdManager getMetaManager()
+    {
+        return getSessionIdManager();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @deprecated always returns 0. no replacement available.
+     */
+    @Deprecated
+    public int getMinSessions()
+    {
+        return 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getRefreshCookieAge()
+    {
+        return _refreshCookieAge;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return same as SessionCookieConfig.getSecure(). If true, session
+     * cookies are ALWAYS marked as secure. If false, a session cookie is
+     * ONLY marked as secure if _secureRequestOnly == true and it is a HTTPS request.
+     */
+    public boolean getSecureCookies()
+    {
+        return _secureCookies;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if session cookie is to be marked as secure only on HTTPS requests
+     */
+    public boolean isSecureRequestOnly()
+    {
+        return _secureRequestOnly;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return if true, session cookie will be marked as secure only iff 
+     * HTTPS request. Can be overridden by setting SessionCookieConfig.setSecure(true),
+     * in which case the session cookie will be marked as secure on both HTTPS and HTTP.
+     */
+    public void setSecureRequestOnly(boolean secureRequestOnly)
+    {
+        _secureRequestOnly = secureRequestOnly;
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    public String getSessionCookie()
+    {
+        return _sessionCookie;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * A sessioncookie is marked as secure IFF any of the following conditions are true:
+     * <ol>
+     * <li>SessionCookieConfig.setSecure == true</li>
+     * <li>SessionCookieConfig.setSecure == false && _secureRequestOnly==true && request is HTTPS</li>
+     * </ol>
+     * According to SessionCookieConfig javadoc, case 1 can be used when:
+     * "... even though the request that initiated the session came over HTTP, 
+     * is to support a topology where the web container is front-ended by an 
+     * SSL offloading load balancer. In this case, the traffic between the client 
+     * and the load balancer will be over HTTPS, whereas the traffic between the 
+     * load balancer and the web container will be over HTTP."
+     * 
+     * For case 2, you can use _secureRequestOnly to determine if you want the
+     * Servlet Spec 3.0  default behaviour when SessionCookieConfig.setSecure==false, 
+     * which is:
+     * "they shall be marked as secure only if the request that initiated the 
+     * corresponding session was also secure"
+     * 
+     * The default for _secureRequestOnly is true, which gives the above behaviour. If
+     * you set it to false, then a session cookie is NEVER marked as secure, even if
+     * the initiating request was secure.
+     * 
+     * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean)
+     */
+    public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure)
+    {
+        if (isUsingCookies())
+        {
+            String sessionPath = (_sessionPath==null) ? contextPath : _sessionPath;
+            sessionPath = (sessionPath==null||sessionPath.length()==0) ? "/" : sessionPath;
+            String id = getNodeId(session);
+            HttpCookie cookie = null;
+            if (_sessionComment == null)
+            {
+                cookie = new HttpCookie(
+                                        _sessionCookie,
+                                        id,
+                                        _sessionDomain,
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure));                  
+            }
+            else
+            {
+                cookie = new HttpCookie(
+                                        _sessionCookie,
+                                        id,
+                                        _sessionDomain,
+                                        sessionPath,
+                                        _cookieConfig.getMaxAge(),
+                                        _cookieConfig.isHttpOnly(),
+                                        _cookieConfig.isSecure() || (isSecureRequestOnly() && requestIsSecure),
+                                        _sessionComment,
+                                        1);    
+            }
+
+            return cookie;
+        }
+        return null;
+    }
+
+    public String getSessionDomain()
+    {
+        return _sessionDomain;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    public SessionHandler getSessionHandler()
+    {
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @deprecated  Need to review if it is needed.
+     */
+    @SuppressWarnings("rawtypes")
+    public Map getSessionMap()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+   
+
+    /* ------------------------------------------------------------ */
+    public int getSessions()
+    {
+        return (int)_sessionsStats.getCurrent();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getSessionIdPathParameterName()
+    {
+        return _sessionIdPathParameterName;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getSessionIdPathParameterNamePrefix()
+    {
+        return _sessionIdPathParameterNamePrefix;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the usingCookies.
+     */
+    public boolean isUsingCookies()
+    {
+        return _usingCookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isValid(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.isValid();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getClusterId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getClusterId();
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getNodeId(HttpSession session)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        return s.getNodeId();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new HttpSession for a request
+     */
+    public HttpSession newHttpSession(HttpServletRequest request)
+    {
+        AbstractSession session=newSession(request);
+        session.setMaxInactiveInterval(_dftMaxIdleSecs);
+        addSession(session,true);
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeEventListener(EventListener listener)
+    {
+        if (listener instanceof HttpSessionAttributeListener)
+            _sessionAttributeListeners.remove(listener);
+        if (listener instanceof HttpSessionListener)
+            _sessionListeners.remove(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see #statsReset()
+     */
+    @Deprecated
+    public void resetStats()
+    {
+        statsReset();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reset statistics values
+     */
+    public void statsReset()
+    {
+        _sessionsStats.reset(getSessions());
+        _sessionTimeStats.reset();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param httpOnly
+     *            The httpOnly to set.
+     */
+    public void setHttpOnly(boolean httpOnly)
+    {
+        _httpOnly=httpOnly;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param metaManager The metaManager used for cross context session management.
+     * @deprecated use {@link #setSessionIdManager(SessionIdManager)}
+     */
+    public void setIdManager(SessionIdManager metaManager)
+    {
+        setSessionIdManager(metaManager);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param metaManager The metaManager used for cross context session management.
+     */
+    public void setSessionIdManager(SessionIdManager metaManager)
+    {
+        _sessionIdManager=metaManager;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds
+     */
+    public void setMaxInactiveInterval(int seconds)
+    {
+        _dftMaxIdleSecs=seconds;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setRefreshCookieAge(int ageInSeconds)
+    {
+        _refreshCookieAge=ageInSeconds;
+    }
+
+
+
+    public void setSessionCookie(String cookieName)
+    {
+        _sessionCookie=cookieName;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler
+     *            The sessionHandler to set.
+     */
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        _sessionHandler=sessionHandler;
+    }
+
+ 
+    /* ------------------------------------------------------------ */
+    public void setSessionIdPathParameterName(String param)
+    {
+        _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param;
+        _sessionIdPathParameterNamePrefix =(param==null||"none".equals(param))?null:(";"+ _sessionIdPathParameterName +"=");
+    }
+    /* ------------------------------------------------------------ */
+    /**
+     * @param usingCookies
+     *            The usingCookies to set.
+     */
+    public void setUsingCookies(boolean usingCookies)
+    {
+        _usingCookies=usingCookies;
+    }
+
+
+    protected abstract void addSession(AbstractSession session);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add the session Registers the session with this manager and registers the
+     * session ID with the sessionIDManager;
+     */
+    protected void addSession(AbstractSession session, boolean created)
+    {
+        synchronized (_sessionIdManager)
+        {
+            _sessionIdManager.addSession(session);
+            addSession(session);
+        }
+
+        if (created)
+        {
+            _sessionsStats.increment();
+            if (_sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener listener : _sessionListeners)
+                    listener.sessionCreated(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a known existing session
+     * @param idInCluster The session ID in the cluster, stripped of any worker name.
+     * @return A Session or null if none exists.
+     */
+    public abstract AbstractSession getSession(String idInCluster);
+
+    protected abstract void invalidateSessions() throws Exception;
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create a new session instance
+     * @param request
+     * @return the new session
+     */
+    protected abstract AbstractSession newSession(HttpServletRequest request);
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the cluster node id (worker id) is returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public boolean isNodeIdInSessionId()
+    {
+        return _nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param nodeIdInSessionId true if the cluster node id (worker id) will be returned as part of the session id by {@link HttpSession#getId()}. Default is false.
+     */
+    public void setNodeIdInSessionId(boolean nodeIdInSessionId)
+    {
+        _nodeIdInSessionId=nodeIdInSessionId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public void removeSession(HttpSession session, boolean invalidate)
+    {
+        AbstractSession s = ((SessionIf)session).getSession();
+        removeSession(s,invalidate);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    public void removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = removeSession(session.getClusterId());
+        
+        if (removed)
+        {
+            _sessionsStats.decrement();
+            _sessionTimeStats.set(round((System.currentTimeMillis() - session.getCreationTime())/1000.0));
+            
+            // Remove session from all context and global id maps
+            _sessionIdManager.removeSession(session);
+            if (invalidate)
+                _sessionIdManager.invalidateAll(session.getClusterId());
+            
+            if (invalidate && _sessionListeners!=null)
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener listener : _sessionListeners)
+                    listener.sessionDestroyed(event);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected abstract boolean removeSession(String idInCluster);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum amount of time session remained valid
+     */
+    public long getSessionTimeMax()
+    {
+        return _sessionTimeStats.getMax();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+    {
+        return __defaultSessionTrackingModes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+    {
+        return Collections.unmodifiableSet(_sessionTrackingModes);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+    {
+        _sessionTrackingModes=new HashSet<SessionTrackingMode>(sessionTrackingModes);
+        _usingCookies=_sessionTrackingModes.contains(SessionTrackingMode.COOKIE);
+        _usingURLs=_sessionTrackingModes.contains(SessionTrackingMode.URL);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isUsingURLs()
+    {
+        return _usingURLs;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public SessionCookieConfig getSessionCookieConfig()
+    {
+        return _cookieConfig;
+    } 
+
+    /* ------------------------------------------------------------ */
+    private SessionCookieConfig _cookieConfig =
+        new SessionCookieConfig()
+        {
+            @Override
+            public String getComment()
+            {
+                return _sessionComment;
+            }
+
+            @Override
+            public String getDomain()
+            {
+                return _sessionDomain;
+            }
+
+            @Override
+            public int getMaxAge()
+            {
+                return _maxCookieAge;
+            }
+
+            @Override
+            public String getName()
+            {
+                return _sessionCookie;
+            }
+
+            @Override
+            public String getPath()
+            {
+                return _sessionPath;
+            }
+
+            @Override
+            public boolean isHttpOnly()
+            {
+                return _httpOnly;
+            }
+
+            @Override
+            public boolean isSecure()
+            {
+                return _secureCookies;
+            }
+
+            @Override
+            public void setComment(String comment)
+            {
+                _sessionComment = comment; 
+            }
+
+            @Override
+            public void setDomain(String domain)
+            {
+                _sessionDomain=domain;
+            }
+
+            @Override
+            public void setHttpOnly(boolean httpOnly)
+            {
+                _httpOnly=httpOnly;
+            }
+
+            @Override
+            public void setMaxAge(int maxAge)
+            {
+                _maxCookieAge=maxAge;
+            }
+
+            @Override
+            public void setName(String name)
+            {
+                _sessionCookie=name;
+            }
+
+            @Override
+            public void setPath(String path)
+            {
+                _sessionPath=path;
+            }
+
+            @Override
+            public void setSecure(boolean secure)
+            {
+                _secureCookies=secure;
+            }
+        
+        };
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total amount of time all sessions remained valid
+     */
+    public long getSessionTimeTotal()
+    {
+        return _sessionTimeStats.getTotal();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return mean amount of time session remained valid
+     */
+    public double getSessionTimeMean()
+    {
+        return _sessionTimeStats.getMean();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return standard deviation of amount of time session remained valid
+     */
+    public double getSessionTimeStdDev()
+    {
+        return _sessionTimeStats.getStdDev();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding()
+     */
+    public boolean isCheckingRemoteSessionIdEncoding()
+    {
+        return _checkingRemoteSessionIdEncoding;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean)
+     */
+    public void setCheckingRemoteSessionIdEncoding(boolean remote)
+    {
+        _checkingRemoteSessionIdEncoding=remote;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Interface that any session wrapper should implement so that
+     * SessionManager may access the Jetty session implementation.
+     *
+     */
+    public interface SessionIf extends HttpSession
+    {
+        public AbstractSession getSession();
+    }
+
+    public void doSessionAttributeListeners(AbstractSession session, String name, Object old, Object value)
+    {
+        if (!_sessionAttributeListeners.isEmpty())
+        {
+            HttpSessionBindingEvent event=new HttpSessionBindingEvent(session,name,old==null?value:old);
+
+            for (HttpSessionAttributeListener l : _sessionAttributeListeners)
+            {
+                if (old==null)
+                    l.attributeAdded(event);
+                else if (value==null)
+                    l.attributeRemoved(event);
+                else
+                    l.attributeReplaced(event);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/session/HashSessionIdManager.java b/src/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
new file mode 100644
index 0000000..ffd0430
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/HashSessionIdManager.java
@@ -0,0 +1,222 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.server.SessionIdManager;
+
+/* ------------------------------------------------------------ */
+/**
+ * HashSessionIdManager. An in-memory implementation of the session ID manager.
+ */
+public class HashSessionIdManager extends AbstractSessionIdManager
+{
+    private final Map<String, Set<WeakReference<HttpSession>>> _sessions = new HashMap<String, Set<WeakReference<HttpSession>>>();
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashSessionIdManager(Random random)
+    {
+        super(random);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of String session IDs
+     */
+    public Collection<String> getSessions()
+    {
+        return Collections.unmodifiableCollection(_sessions.keySet());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Collection of Sessions for the passed session ID
+     */
+    public Collection<HttpSession> getSession(String id)
+    {
+        ArrayList<HttpSession> sessions = new ArrayList<HttpSession>();
+        Set<WeakReference<HttpSession>> refs =_sessions.get(id);
+        if (refs!=null)
+        {
+            for (WeakReference<HttpSession> ref: refs)
+            {
+                HttpSession session = ref.get();
+                if (session!=null)
+                    sessions.add(session);
+            }
+        }
+        return sessions;
+    }
+    /* ------------------------------------------------------------ */
+    /** Get the session ID with any worker ID.
+     * 
+     * @param clusterId
+     * @param request
+     * @return sessionId plus any worker ID.
+     */
+    public String getNodeId(String clusterId,HttpServletRequest request) 
+    {
+        // used in Ajp13Parser
+        String worker=request==null?null:(String)request.getAttribute("org.eclipse.jetty.ajp.JVMRoute");
+        if (worker!=null) 
+            return clusterId+'.'+worker; 
+        
+        if (_workerName!=null) 
+            return clusterId+'.'+_workerName;
+       
+        return clusterId;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the session ID without any worker ID.
+     * 
+     * @param nodeId the node id
+     * @return sessionId without any worker ID.
+     */
+    public String getClusterId(String nodeId) 
+    {
+        int dot=nodeId.lastIndexOf('.');
+        return (dot>0)?nodeId.substring(0,dot):nodeId;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {        
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _sessions.clear(); 
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#idInUse(String)
+     */
+    public boolean idInUse(String id)
+    {
+        synchronized (this)
+        {
+            return _sessions.containsKey(id);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#addSession(HttpSession)
+     */
+    public void addSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+        WeakReference<HttpSession> ref = new WeakReference<HttpSession>(session);
+        
+        synchronized (this)
+        {
+            Set<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions==null)
+            {
+                sessions=new HashSet<WeakReference<HttpSession>>();
+                _sessions.put(id,sessions);
+            }
+            sessions.add(ref);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#removeSession(HttpSession)
+     */
+    public void removeSession(HttpSession session)
+    {
+        String id = getClusterId(session.getId());
+        
+        synchronized (this)
+        {
+            Collection<WeakReference<HttpSession>> sessions = _sessions.get(id);
+            if (sessions!=null)
+            {
+                for (Iterator<WeakReference<HttpSession>> iter = sessions.iterator(); iter.hasNext();)
+                {
+                    WeakReference<HttpSession> ref = iter.next();
+                    HttpSession s=ref.get();
+                    if (s==null)
+                    {
+                        iter.remove();
+                        continue;
+                    }
+                    if (s==session)
+                    {
+                        iter.remove();
+                        break;
+                    }
+                }
+                if (sessions.isEmpty())
+                    _sessions.remove(id);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see SessionIdManager#invalidateAll(String)
+     */
+    public void invalidateAll(String id)
+    {
+        Collection<WeakReference<HttpSession>> sessions;
+        synchronized (this)
+        {
+            sessions = _sessions.remove(id);
+        }
+        
+        if (sessions!=null)
+        {
+            for (WeakReference<HttpSession> ref: sessions)
+            {
+                AbstractSession session=(AbstractSession)ref.get();
+                if (session!=null && session.isValid())
+                    session.invalidate();
+            }
+            sessions.clear();
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/session/HashSessionManager.java b/src/java/org/eclipse/jetty/server/session/HashSessionManager.java
new file mode 100644
index 0000000..49bce68
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/HashSessionManager.java
@@ -0,0 +1,640 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * HashSessionManager
+ * 
+ * An in-memory implementation of SessionManager.
+ * <p>
+ * This manager supports saving sessions to disk, either periodically or at shutdown.
+ * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
+ * <p>
+ * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
+ * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
+ *
+ */
+public class HashSessionManager extends AbstractSessionManager
+{
+    final static Logger __log = SessionHandler.LOG;
+
+    protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
+    private static int __id;
+    private Timer _timer;
+    private boolean _timerStop=false;
+    private TimerTask _task;
+    long _scavengePeriodMs=30000;
+    long _savePeriodMs=0; //don't do period saves by default
+    long _idleSavePeriodMs = 0; // don't idle save sessions by default.
+    private TimerTask _saveTask;
+    File _storeDir;
+    private boolean _lazyLoad=false;
+    private volatile boolean _sessionsLoaded=false;
+    private boolean _deleteUnrestorableSessions=false;
+    
+
+
+
+    /* ------------------------------------------------------------ */
+    public HashSessionManager()
+    {
+        super();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        super.doStart();
+
+        _timerStop=false;
+        ServletContext context = ContextHandler.getCurrentContext();
+        if (context!=null)
+            _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer");
+        if (_timer==null)
+        {
+            _timerStop=true;
+            _timer=new Timer("HashSessionScavenger-"+__id++, true);
+        }
+
+        setScavengePeriod(getScavengePeriod());
+
+        if (_storeDir!=null)
+        {
+            if (!_storeDir.exists())
+                _storeDir.mkdirs();
+
+            if (!_lazyLoad)
+                restoreSessions();
+        }
+
+        setSavePeriod(getSavePeriod());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        // stop the scavengers
+        synchronized(this)
+        {
+            if (_saveTask!=null)
+                _saveTask.cancel();
+            _saveTask=null;
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            if (_timer!=null && _timerStop)
+                _timer.cancel();
+            _timer=null;
+        }
+
+        // This will callback invalidate sessions - where we decide if we will save
+        super.doStop();
+
+        _sessions.clear();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public int getScavengePeriod()
+    {
+        return (int)(_scavengePeriodMs/1000);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int getSessions()
+    {
+        int sessions=super.getSessions();
+        if (__log.isDebugEnabled())
+        {
+            if (_sessions.size()!=sessions)
+                __log.warn("sessions: "+_sessions.size()+"!="+sessions);
+        }
+        return sessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return seconds Idle period after which a session is saved
+     */
+    public int getIdleSavePeriod()
+    {
+      if (_idleSavePeriodMs <= 0)
+        return 0;
+
+      return (int)(_idleSavePeriodMs / 1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Configures the period in seconds after which a session is deemed idle and saved
+     * to save on session memory.
+     *
+     * The session is persisted, the values attribute map is cleared and the session set to idled.
+     *
+     * @param seconds Idle period after which a session is saved
+     */
+    public void setIdleSavePeriod(int seconds)
+    {
+      _idleSavePeriodMs = seconds * 1000L;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setMaxInactiveInterval(int seconds)
+    {
+        super.setMaxInactiveInterval(seconds);
+        if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
+            setScavengePeriod((_dftMaxIdleSecs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period is seconds at which sessions are periodically saved to disk
+     */
+    public void setSavePeriod (int seconds)
+    {
+        long period = (seconds * 1000L);
+        if (period < 0)
+            period=0;
+        _savePeriodMs=period;
+
+        if (_timer!=null)
+        {
+            synchronized (this)
+            {
+                if (_saveTask!=null)
+                    _saveTask.cancel();
+                if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
+                {
+                    _saveTask = new TimerTask()
+                    {
+                        @Override
+                        public void run()
+                        {
+                            try
+                            {
+                                saveSessions(true);
+                            }
+                            catch (Exception e)
+                            {
+                                __log.warn(e);
+                            }
+                        }
+                    };
+                    _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the period in seconds at which sessions are periodically saved to disk
+     */
+    public int getSavePeriod ()
+    {
+        if (_savePeriodMs<=0)
+            return 0;
+
+        return (int)(_savePeriodMs/1000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
+     */
+    public void setScavengePeriod(int seconds)
+    { 
+        if (seconds==0)
+            seconds=60;
+
+        long old_period=_scavengePeriodMs;
+        long period=seconds*1000L;
+        if (period>60000)
+            period=60000;
+        if (period<1000)
+            period=1000;
+
+        _scavengePeriodMs=period;
+    
+        if (_timer!=null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                    _task.cancel();
+                _task = new TimerTask()
+                {
+                    @Override
+                    public void run()
+                    {
+                        scavenge();
+                    }
+                };
+                _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /**
+     * Find sessions that have timed out and invalidate them. This runs in the
+     * SessionScavenger thread.
+     */
+    protected void scavenge()
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return;
+
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        try
+        {
+            if (_loader!=null)
+                thread.setContextClassLoader(_loader);
+
+            // For each session
+            long now=System.currentTimeMillis();
+          
+            for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
+            {
+                HashedSession session=i.next();
+                long idleTime=session.getMaxInactiveInterval()*1000L; 
+                if (idleTime>0&&session.getAccessed()+idleTime<now)
+                {
+                    // Found a stale session, add it to the list
+                    try
+                    {
+                        session.timeout();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem scavenging sessions", e);
+                    }
+                }
+                else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
+                {
+                    try
+                    {
+                        session.idle();
+                    }
+                    catch (Exception e)
+                    {
+                        __log.warn("Problem idling session "+ session.getId(), e);
+                    }
+                }
+            }
+        }       
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (isRunning())
+            _sessions.put(session.getClusterId(),(HashedSession)session);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public AbstractSession getSession(String idInCluster)
+    {
+        if ( _lazyLoad && !_sessionsLoaded)
+        {
+            try
+            {
+                restoreSessions();
+            }
+            catch(Exception e)
+            {
+                __log.warn(e);
+            }
+        }
+
+        Map<String,HashedSession> sessions=_sessions;
+        if (sessions==null)
+            return null;
+
+        HashedSession session = sessions.get(idInCluster);
+
+        if (session == null && _lazyLoad)
+            session=restoreSession(idInCluster);
+        if (session == null)
+            return null;
+
+        if (_idleSavePeriodMs!=0)
+            session.deIdle();
+
+        return session;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void invalidateSessions() throws Exception
+    {
+        // Invalidate all sessions to cause unbind events
+        ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
+        int loop=100;
+        while (sessions.size()>0 && loop-->0)
+        {
+            // If we are called from doStop
+            if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
+            {
+                // Then we only save and remove the session - it is not invalidated.
+                for (HashedSession session : sessions)
+                {
+                    session.save(false);
+                    removeSession(session,false);
+                }
+            }
+            else
+            {
+                for (HashedSession session : sessions)
+                    session.invalidate();
+            }
+
+            // check that no new sessions were created while we were iterating
+            sessions=new ArrayList<HashedSession>(_sessions.values());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new HashedSession(this, request);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected AbstractSession newSession(long created, long accessed, String clusterId)
+    {
+        return new HashedSession(this, created,accessed, clusterId);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean removeSession(String clusterId)
+    {
+        return _sessions.remove(clusterId)!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setStoreDirectory (File dir) throws IOException
+    { 
+        // CanonicalFile is used to capture the base store directory in a way that will
+        // work on Windows.  Case differences may through off later checks using this directory.
+        _storeDir=dir.getCanonicalFile();
+    }
+
+    /* ------------------------------------------------------------ */
+    public File getStoreDirectory ()
+    {
+        return _storeDir;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setLazyLoad(boolean lazyLoad)
+    {
+        _lazyLoad = lazyLoad;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLazyLoad()
+    {
+        return _lazyLoad;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean isDeleteUnrestorableSessions()
+    {
+        return _deleteUnrestorableSessions;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
+    {
+        _deleteUnrestorableSessions = deleteUnrestorableSessions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void restoreSessions () throws Exception
+    {
+        _sessionsLoaded = true;
+
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canRead())
+        {
+            __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
+            return;
+        }
+
+        String[] files = _storeDir.list();
+        for (int i=0;files!=null&&i<files.length;i++)
+        {
+            restoreSession(files[i]);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized HashedSession restoreSession(String idInCuster)
+    {        
+        File file = new File(_storeDir,idInCuster);
+
+        FileInputStream in = null;
+        Exception error = null;
+        try
+        {
+            if (file.exists())
+            {
+                in = new FileInputStream(file);
+                HashedSession session = restoreSession(in, null);
+                addSession(session, false);
+                session.didActivate();
+                return session;
+            }
+        }
+        catch (Exception e)
+        {
+           error = e;
+        }
+        finally
+        {
+            if (in != null) IO.close(in);
+            
+            if (error != null)
+            {
+                if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
+                {
+                    file.delete();
+                    __log.warn("Deleting file for unrestorable session "+idInCuster, error);
+                }
+                else
+                {
+                    __log.warn("Problem restoring session "+idInCuster, error);
+                }
+            }
+            else
+               file.delete(); //delete successfully restored file
+                
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void saveSessions(boolean reactivate) throws Exception
+    {
+        if (_storeDir==null || !_storeDir.exists())
+        {
+            return;
+        }
+
+        if (!_storeDir.canWrite())
+        {
+            __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
+            return;
+        }
+
+        for (HashedSession session : _sessions.values())
+            session.save(true);
+    }
+
+    /* ------------------------------------------------------------ */
+    public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
+    {
+        /*
+         * Take care of this class's fields first by calling
+         * defaultReadObject
+         */
+        DataInputStream in = new DataInputStream(is);
+        try
+        {
+            String clusterId = in.readUTF();
+            in.readUTF(); // nodeId
+            long created = in.readLong();
+            long accessed = in.readLong();
+            int requests = in.readInt();
+
+            if (session == null)
+                session = (HashedSession)newSession(created, accessed, clusterId);
+            session.setRequests(requests);
+            int size = in.readInt();
+            if (size>0)
+            {
+                ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
+                try
+                {
+                    for (int i=0; i<size;i++)
+                    {
+                        String key = ois.readUTF();
+                        Object value = ois.readObject();
+                        session.setAttribute(key,value);
+                    }
+                }
+                finally
+                {
+                    IO.close(ois);
+                }
+            }
+            return session;
+        }
+        finally
+        {
+            IO.close(in);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class ClassLoadingObjectInputStream extends ObjectInputStream
+    {
+        /* ------------------------------------------------------------ */
+        public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+        {
+            super(in);
+        }
+
+        /* ------------------------------------------------------------ */
+        public ClassLoadingObjectInputStream () throws IOException
+        {
+            super();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+        {
+            try
+            {
+                return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+            }
+            catch (ClassNotFoundException e)
+            {
+                return super.resolveClass(cl);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/session/HashedSession.java b/src/java/org/eclipse/jetty/server/session/HashedSession.java
new file mode 100644
index 0000000..d3251e1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/HashedSession.java
@@ -0,0 +1,241 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class HashedSession extends AbstractSession
+{
+    private static final Logger LOG = Log.getLogger(HashedSession.class);
+
+    private final HashSessionManager _hashSessionManager;
+
+    /** Whether the session has been saved because it has been deemed idle; 
+     * in which case its attribute map will have been saved and cleared. */
+    private transient boolean _idled = false;
+
+    /** Whether there has already been an attempt to save this session
+     * which has failed.  If there has, there will be no more save attempts
+     * for this session.  This is to stop the logs being flooded with errors
+     * due to serialization failures that are most likely caused by user
+     * data stored in the session that is not serializable. */
+    private transient boolean _saveFailed = false;
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, HttpServletRequest request)
+    {
+        super(hashSessionManager,request);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected HashedSession(HashSessionManager hashSessionManager, long created, long accessed, String clusterId)
+    {
+        super(hashSessionManager,created, accessed, clusterId);
+        _hashSessionManager = hashSessionManager;
+    }
+
+    /* ------------------------------------------------------------- */
+    protected void checkValid()
+    {
+        if (_hashSessionManager._idleSavePeriodMs!=0)
+            deIdle();
+        super.checkValid();
+    }
+    
+    /* ------------------------------------------------------------- */
+    @Override
+    public void setMaxInactiveInterval(int secs)
+    {
+        super.setMaxInactiveInterval(secs);
+        if (getMaxInactiveInterval()>0&&(getMaxInactiveInterval()*1000L/10)<_hashSessionManager._scavengePeriodMs)
+            _hashSessionManager.setScavengePeriod((secs+9)/10);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doInvalidate()
+    throws IllegalStateException
+    {
+        super.doInvalidate();
+        
+        // Remove from the disk
+        if (_hashSessionManager._storeDir!=null && getId()!=null)
+        {
+            String id=getId();
+            File f = new File(_hashSessionManager._storeDir, id);
+            f.delete();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    synchronized void save(boolean reactivate)
+    throws Exception
+    {
+        // Only idle the session if not already idled and no previous save/idle has failed
+        if (!isIdled() && !_saveFailed)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Saving {} {}",super.getId(),reactivate);
+
+            File file = null;
+            FileOutputStream fos = null;
+            
+            try
+            {
+                file = new File(_hashSessionManager._storeDir, super.getId());
+
+                if (file.exists())
+                    file.delete();
+                file.createNewFile();
+                fos = new FileOutputStream(file);
+                willPassivate();
+                save(fos);
+                IO.close(fos);
+                if (reactivate)
+                    didActivate();
+                else
+                    clearAttributes();
+            }
+            catch (Exception e)
+            {
+                saveFailed(); // We won't try again for this session
+                if (fos != null) IO.close(fos);
+                if (file != null) file.delete(); // No point keeping the file if we didn't save the whole session
+                throw e;             
+            }
+        }
+    }
+    /* ------------------------------------------------------------ */
+    public synchronized void save(OutputStream os)  throws IOException 
+    {
+        DataOutputStream out = new DataOutputStream(os);
+        out.writeUTF(getClusterId());
+        out.writeUTF(getNodeId());
+        out.writeLong(getCreationTime());
+        out.writeLong(getAccessed());
+        
+        /* Don't write these out, as they don't make sense to store because they
+         * either they cannot be true or their value will be restored in the 
+         * Session constructor.
+         */
+        //out.writeBoolean(_invalid);
+        //out.writeBoolean(_doInvalidate);
+        //out.writeLong(_maxIdleMs);
+        //out.writeBoolean( _newSession);
+        out.writeInt(getRequests());
+        out.writeInt(getAttributes());
+        ObjectOutputStream oos = new ObjectOutputStream(out);
+        Enumeration<String> e=getAttributeNames();
+        while(e.hasMoreElements())
+        {
+            String key=e.nextElement();
+            oos.writeUTF(key);
+            oos.writeObject(doGet(key));
+        }
+        oos.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void deIdle()
+    {
+        if (isIdled())
+        {
+            // Access now to prevent race with idling period
+            access(System.currentTimeMillis());
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("De-idling " + super.getId());
+
+            FileInputStream fis = null;
+
+            try
+            {
+                File file = new File(_hashSessionManager._storeDir, super.getId());
+                if (!file.exists() || !file.canRead())
+                    throw new FileNotFoundException(file.getName());
+
+                fis = new FileInputStream(file);
+                _idled = false;
+                _hashSessionManager.restoreSession(fis, this);
+                IO.close(fis); 
+                
+                didActivate();
+
+                // If we are doing period saves, then there is no point deleting at this point 
+                if (_hashSessionManager._savePeriodMs == 0)
+                    file.delete();
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem de-idling session " + super.getId(), e);
+                if (fis != null) IO.close(fis);//Must ensure closed before invalidate
+                invalidate();
+            }
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Idle the session to reduce session memory footprint.
+     * 
+     * The session is idled by persisting it, then clearing the session values attribute map and finally setting 
+     * it to an idled state.  
+     */
+    public synchronized void idle()
+    throws Exception
+    {
+        save(false);
+        _idled = true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isIdled()
+    {
+      return _idled;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized boolean isSaveFailed()
+    {
+        return _saveFailed;
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void saveFailed()
+    {
+        _saveFailed = true;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/src/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
new file mode 100644
index 0000000..05a82d1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java
@@ -0,0 +1,1040 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.sql.Blob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Random;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.naming.InitialContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.sql.DataSource;
+
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * JDBCSessionIdManager
+ *
+ * SessionIdManager implementation that uses a database to store in-use session ids, 
+ * to support distributed sessions.
+ * 
+ */
+public class JDBCSessionIdManager extends AbstractSessionIdManager
+{    
+    final static Logger LOG = SessionHandler.LOG;
+    
+    protected final HashSet<String> _sessionIds = new HashSet<String>();
+    protected Server _server;
+    protected Driver _driver;
+    protected String _driverClassName;
+    protected String _connectionUrl;
+    protected DataSource _datasource;
+    protected String _jndiName;
+    protected String _sessionIdTable = "JettySessionIds";
+    protected String _sessionTable = "JettySessions";
+    protected String _sessionTableRowId = "rowId";
+    
+    protected Timer _timer; //scavenge timer
+    protected TimerTask _task; //scavenge task
+    protected long _lastScavengeTime;
+    protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
+    protected String _blobType; //if not set, is deduced from the type of the database at runtime
+    protected String _longType; //if not set, is deduced from the type of the database at runtime
+    
+    protected String _createSessionIdTable;
+    protected String _createSessionTable;
+                                            
+    protected String _selectBoundedExpiredSessions;
+    protected String _deleteOldExpiredSessions;
+
+    protected String _insertId;
+    protected String _deleteId;
+    protected String _queryId;
+    
+    protected  String _insertSession;
+    protected  String _deleteSession;
+    protected  String _updateSession;
+    protected  String _updateSessionNode;
+    protected  String _updateSessionAccessTime;
+    
+    protected DatabaseAdaptor _dbAdaptor;
+
+    private String _selectExpiredSessions;
+
+    
+    /**
+     * DatabaseAdaptor
+     *
+     * Handles differences between databases.
+     * 
+     * Postgres uses the getBytes and setBinaryStream methods to access
+     * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
+     * is happy to use the "blob" type and getBlob() methods instead.
+     * 
+     * TODO if the differences become more major it would be worthwhile
+     * refactoring this class.
+     */
+    public class DatabaseAdaptor 
+    {
+        String _dbName;
+        boolean _isLower;
+        boolean _isUpper;
+       
+        
+        
+        public DatabaseAdaptor (DatabaseMetaData dbMeta)
+        throws SQLException
+        {
+            _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH); 
+            LOG.debug ("Using database {}",_dbName);
+            _isLower = dbMeta.storesLowerCaseIdentifiers();
+            _isUpper = dbMeta.storesUpperCaseIdentifiers();            
+        }
+        
+        /**
+         * Convert a camel case identifier into either upper or lower
+         * depending on the way the db stores identifiers.
+         * 
+         * @param identifier
+         * @return the converted identifier
+         */
+        public String convertIdentifier (String identifier)
+        {
+            if (_isLower)
+                return identifier.toLowerCase(Locale.ENGLISH);
+            if (_isUpper)
+                return identifier.toUpperCase(Locale.ENGLISH);
+            
+            return identifier;
+        }
+        
+        public String getDBName ()
+        {
+            return _dbName;
+        }
+        
+        public String getBlobType ()
+        {
+            if (_blobType != null)
+                return _blobType;
+            
+            if (_dbName.startsWith("postgres"))
+                return "bytea";
+            
+            return "blob";
+        }
+        
+        public String getLongType ()
+        {
+            if (_longType != null)
+                return _longType;
+            
+            if (_dbName.startsWith("oracle"))
+                return "number(20)";
+            
+            return "bigint";
+        }
+        
+        public InputStream getBlobInputStream (ResultSet result, String columnName)
+        throws SQLException
+        {
+            if (_dbName.startsWith("postgres"))
+            {
+                byte[] bytes = result.getBytes(columnName);
+                return new ByteArrayInputStream(bytes);
+            }
+            
+            Blob blob = result.getBlob(columnName);
+            return blob.getBinaryStream();
+        }
+        
+        /**
+         * rowId is a reserved word for Oracle, so change the name of this column
+         * @return
+         */
+        public String getRowIdColumnName ()
+        {
+            if (_dbName != null && _dbName.startsWith("oracle"))
+                return "srowId";
+            
+            return "rowId";
+        }
+        
+        
+        public boolean isEmptyStringNull ()
+        {
+            return (_dbName.startsWith("oracle"));
+        }
+        
+        public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts) 
+        throws SQLException
+        {
+            if (contextPath == null || "".equals(contextPath))
+            {
+                if (isEmptyStringNull())
+                {
+                    PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
+                    " where sessionId = ? and contextPath is null and virtualHost = ?");
+                    statement.setString(1, rowId);
+                    statement.setString(2, virtualHosts);
+
+                    return statement;
+                }
+            }
+           
+
+
+            PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
+            " where sessionId = ? and contextPath = ? and virtualHost = ?");
+            statement.setString(1, rowId);
+            statement.setString(2, contextPath);
+            statement.setString(3, virtualHosts);
+
+            return statement;
+        }
+    }
+    
+    
+    
+    public JDBCSessionIdManager(Server server)
+    {
+        super();
+        _server=server;
+    }
+    
+    public JDBCSessionIdManager(Server server, Random random)
+    {
+       super(random);
+       _server=server;
+    }
+
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     * 
+     * @param driverClassName
+     * @param connectionUrl
+     */
+    public void setDriverInfo (String driverClassName, String connectionUrl)
+    {
+        _driverClassName=driverClassName;
+        _connectionUrl=connectionUrl;
+    }
+    
+    /**
+     * Configure jdbc connection information via a jdbc Driver
+     * 
+     * @param driverClass
+     * @param connectionUrl
+     */
+    public void setDriverInfo (Driver driverClass, String connectionUrl)
+    {
+        _driver=driverClass;
+        _connectionUrl=connectionUrl;
+    }
+    
+    
+    public void setDatasource (DataSource ds)
+    {
+        _datasource = ds;
+    }
+    
+    public DataSource getDataSource ()
+    {
+        return _datasource;
+    }
+    
+    public String getDriverClassName()
+    {
+        return _driverClassName;
+    }
+    
+    public String getConnectionUrl ()
+    {
+        return _connectionUrl;
+    }
+    
+    public void setDatasourceName (String jndi)
+    {
+        _jndiName=jndi;
+    }
+    
+    public String getDatasourceName ()
+    {
+        return _jndiName;
+    }
+   
+    public void setBlobType (String name)
+    {
+        _blobType = name;
+    }
+    
+    public String getBlobType ()
+    {
+        return _blobType;
+    }
+    
+    
+    
+    public String getLongType()
+    {
+        return _longType;
+    }
+
+    public void setLongType(String longType)
+    {
+        this._longType = longType;
+    }
+
+    public void setScavengeInterval (long sec)
+    {
+        if (sec<=0)
+            sec=60;
+
+        long old_period=_scavengeIntervalMs;
+        long period=sec*1000L;
+      
+        _scavengeIntervalMs=period;
+        
+        //add a bit of variability into the scavenge time so that not all
+        //nodes with the same scavenge time sync up
+        long tenPercent = _scavengeIntervalMs/10;
+        if ((System.currentTimeMillis()%2) == 0)
+            _scavengeIntervalMs += tenPercent;
+        
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
+        if (_timer!=null && (period!=old_period || _task==null))
+        {
+            synchronized (this)
+            {
+                if (_task!=null)
+                    _task.cancel();
+                _task = new TimerTask()
+                {
+                    @Override
+                    public void run()
+                    {
+                        scavenge();
+                    }   
+                };
+                _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs);
+            }
+        }  
+    }
+    
+    public long getScavengeInterval ()
+    {
+        return _scavengeIntervalMs/1000;
+    }
+    
+    
+    public void addSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+        
+        synchronized (_sessionIds)
+        {
+            String id = ((JDBCSessionManager.Session)session).getClusterId();            
+            try
+            {
+                insert(id);
+                _sessionIds.add(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem storing session id="+id, e);
+            }
+        }
+    }
+    
+    public void removeSession(HttpSession session)
+    {
+        if (session == null)
+            return;
+        
+        removeSession(((JDBCSessionManager.Session)session).getClusterId());
+    }
+    
+    
+    
+    public void removeSession (String id)
+    {
+
+        if (id == null)
+            return;
+        
+        synchronized (_sessionIds)
+        {  
+            if (LOG.isDebugEnabled())
+                LOG.debug("Removing session id="+id);
+            try
+            {               
+                _sessionIds.remove(id);
+                delete(id);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem removing session id="+id, e);
+            }
+        }
+        
+    }
+    
+
+    /** 
+     * Get the session id without any node identifier suffix.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String)
+     */
+    public String getClusterId(String nodeId)
+    {
+        int dot=nodeId.lastIndexOf('.');
+        return (dot>0)?nodeId.substring(0,dot):nodeId;
+    }
+    
+
+    /** 
+     * Get the session id, including this node's id as a suffix.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest)
+     */
+    public String getNodeId(String clusterId, HttpServletRequest request)
+    {
+        if (_workerName!=null)
+            return clusterId+'.'+_workerName;
+
+        return clusterId;
+    }
+
+
+    public boolean idInUse(String id)
+    {
+        if (id == null)
+            return false;
+        
+        String clusterId = getClusterId(id);
+        boolean inUse = false;
+        synchronized (_sessionIds)
+        {
+            inUse = _sessionIds.contains(clusterId);
+        }
+        
+        
+        if (inUse)
+            return true; //optimisation - if this session is one we've been managing, we can check locally
+
+        //otherwise, we need to go to the database to check
+        try
+        {
+            return exists(clusterId);
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Problem checking inUse for id="+clusterId, e);
+            return false;
+        }
+    }
+
+    /** 
+     * Invalidate the session matching the id on all contexts.
+     * 
+     * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
+     */
+    public void invalidateAll(String id)
+    {            
+        //take the id out of the list of known sessionids for this node
+        removeSession(id);
+        
+        synchronized (_sessionIds)
+        {
+            //tell all contexts that may have a session object with this id to
+            //get rid of them
+            Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+            for (int i=0; contexts!=null && i<contexts.length; i++)
+            {
+                SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                if (sessionHandler != null) 
+                {
+                    SessionManager manager = sessionHandler.getSessionManager();
+
+                    if (manager != null && manager instanceof JDBCSessionManager)
+                    {
+                        ((JDBCSessionManager)manager).invalidateSession(id);
+                    }
+                }
+            }
+        }
+    }
+
+
+    /** 
+     * Start up the id manager.
+     * 
+     * Makes necessary database tables and starts a Session
+     * scavenger thread.
+     */
+    @Override
+    public void doStart()
+    throws Exception
+    {           
+        initializeDatabase();
+        prepareTables();   
+        cleanExpiredSessions();
+        super.doStart();
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
+        _timer=new Timer("JDBCSessionScavenger", true);
+        setScavengeInterval(getScavengeInterval());
+    }
+
+    /** 
+     * Stop the scavenger.
+     */
+    @Override
+    public void doStop () 
+    throws Exception
+    {
+        synchronized(this)
+        {
+            if (_task!=null)
+                _task.cancel();
+            if (_timer!=null)
+                _timer.cancel();
+            _timer=null;
+        }
+        _sessionIds.clear();
+        super.doStop();
+    }
+  
+    /**
+     * Get a connection from the driver or datasource.
+     * 
+     * @return the connection for the datasource
+     * @throws SQLException
+     */
+    protected Connection getConnection ()
+    throws SQLException
+    {
+        if (_datasource != null)
+            return _datasource.getConnection();
+        else
+            return DriverManager.getConnection(_connectionUrl);
+    }
+    
+    
+   
+    
+    
+    /**
+     * Set up the tables in the database
+     * @throws SQLException
+     */
+    private void prepareTables()
+    throws SQLException
+    {
+        _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))";
+        _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?";
+        _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
+        _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
+
+        _insertId = "insert into "+_sessionIdTable+" (id)  values (?)";
+        _deleteId = "delete from "+_sessionIdTable+" where id = ?";
+        _queryId = "select * from "+_sessionIdTable+" where id = ?";
+
+        Connection connection = null;
+        try
+        {
+            //make the id table
+            connection = getConnection();
+            connection.setAutoCommit(true);
+            DatabaseMetaData metaData = connection.getMetaData();
+            _dbAdaptor = new DatabaseAdaptor(metaData);
+            _sessionTableRowId = _dbAdaptor.getRowIdColumnName();
+
+            //checking for table existence is case-sensitive, but table creation is not
+            String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable);
+            ResultSet result = metaData.getTables(null, null, tableName, null);
+            if (!result.next())
+            {
+                //table does not exist, so create it
+                connection.createStatement().executeUpdate(_createSessionIdTable);
+            }
+            
+            //make the session table if necessary
+            tableName = _dbAdaptor.convertIdentifier(_sessionTable);   
+            result = metaData.getTables(null, null, tableName, null);
+            if (!result.next())
+            {
+                //table does not exist, so create it
+                String blobType = _dbAdaptor.getBlobType();
+                String longType = _dbAdaptor.getLongType();
+                _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+
+                                           " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
+                                           " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
+                                           " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
+                connection.createStatement().executeUpdate(_createSessionTable);
+            }
+            
+            //make some indexes on the JettySessions table
+            String index1 = "idx_"+_sessionTable+"_expiry";
+            String index2 = "idx_"+_sessionTable+"_session";
+            
+            result = metaData.getIndexInfo(null, null, tableName, false, false);
+            boolean index1Exists = false;
+            boolean index2Exists = false;
+            while (result.next())
+            {
+                String idxName = result.getString("INDEX_NAME");
+                if (index1.equalsIgnoreCase(idxName))
+                    index1Exists = true;
+                else if (index2.equalsIgnoreCase(idxName))
+                    index2Exists = true;
+            }
+            if (!(index1Exists && index2Exists))
+            {
+                Statement statement = connection.createStatement();
+                try
+                {
+                    if (!index1Exists)
+                        statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)");
+                    if (!index2Exists)
+                        statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)");
+                }
+                finally
+                {
+                    if (statement!=null)
+                    {
+                        try { statement.close(); }
+                        catch(Exception e) { LOG.warn(e); }
+                    }
+                }
+            }
+
+            //set up some strings representing the statements for session manipulation
+            _insertSession = "insert into "+_sessionTable+
+            " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
+            " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+            _deleteSession = "delete from "+_sessionTable+
+            " where "+_sessionTableRowId+" = ?";
+            
+            _updateSession = "update "+_sessionTable+
+            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?";
+
+            _updateSessionNode = "update "+_sessionTable+
+            " set lastNode = ? where "+_sessionTableRowId+" = ?";
+
+            _updateSessionAccessTime = "update "+_sessionTable+
+            " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?";
+
+            
+        }
+        finally
+        {
+            if (connection != null)
+                connection.close();
+        }
+    }
+    
+    /**
+     * Insert a new used session id into the table.
+     * 
+     * @param id
+     * @throws SQLException
+     */
+    private void insert (String id)
+    throws SQLException 
+    {
+        Connection connection = null;
+        PreparedStatement statement = null;
+        PreparedStatement query = null;
+        try
+        {
+            connection = getConnection();
+            connection.setAutoCommit(true);            
+            query = connection.prepareStatement(_queryId);
+            query.setString(1, id);
+            ResultSet result = query.executeQuery();
+            //only insert the id if it isn't in the db already 
+            if (!result.next())
+            {
+                statement = connection.prepareStatement(_insertId);
+                statement.setString(1, id);
+                statement.executeUpdate();
+            }
+        }
+        finally
+        {
+            if (query!=null)
+            {
+                try { query.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection != null)
+                connection.close();
+        }
+    }
+    
+    /**
+     * Remove a session id from the table.
+     * 
+     * @param id
+     * @throws SQLException
+     */
+    private void delete (String id)
+    throws SQLException
+    {
+        Connection connection = null;
+        PreparedStatement statement = null;
+        try
+        {
+            connection = getConnection();
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_deleteId);
+            statement.setString(1, id);
+            statement.executeUpdate();
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection != null)
+                connection.close();
+        }
+    }
+    
+    
+    /**
+     * Check if a session id exists.
+     * 
+     * @param id
+     * @return
+     * @throws SQLException
+     */
+    private boolean exists (String id)
+    throws SQLException
+    {
+        Connection connection = null;
+        PreparedStatement statement = null;
+        try
+        {
+            connection = getConnection();
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_queryId);
+            statement.setString(1, id);
+            ResultSet result = statement.executeQuery();
+            return result.next();
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection != null)
+                connection.close();
+        }
+    }
+    
+    /**
+     * Look for sessions in the database that have expired.
+     * 
+     * We do this in the SessionIdManager and not the SessionManager so
+     * that we only have 1 scavenger, otherwise if there are n SessionManagers
+     * there would be n scavengers, all contending for the database.
+     * 
+     * We look first for sessions that expired in the previous interval, then
+     * for sessions that expired previously - these are old sessions that no
+     * node is managing any more and have become stuck in the database.
+     */
+    private void scavenge ()
+    {
+        Connection connection = null;
+        PreparedStatement statement = null;
+        List<String> expiredSessionIds = new ArrayList<String>();
+        try
+        {            
+            if (LOG.isDebugEnabled()) 
+                LOG.debug("Scavenge sweep started at "+System.currentTimeMillis());
+            if (_lastScavengeTime > 0)
+            {
+                connection = getConnection();
+                connection.setAutoCommit(true);
+                //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime";
+                statement = connection.prepareStatement(_selectBoundedExpiredSessions);
+                long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
+                long upperBound = _lastScavengeTime;
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound);
+                
+                statement.setLong(1, lowerBound);
+                statement.setLong(2, upperBound);
+                ResultSet result = statement.executeQuery();
+                while (result.next())
+                {
+                    String sessionId = result.getString("sessionId");
+                    expiredSessionIds.add(sessionId);
+                    if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId); 
+                }
+
+                //tell the SessionManagers to expire any sessions with a matching sessionId in memory
+                Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
+                for (int i=0; contexts!=null && i<contexts.length; i++)
+                {
+
+                    SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
+                    if (sessionHandler != null) 
+                    { 
+                        SessionManager manager = sessionHandler.getSessionManager();
+                        if (manager != null && manager instanceof JDBCSessionManager)
+                        {
+                            ((JDBCSessionManager)manager).expire(expiredSessionIds);
+                        }
+                    }
+                }
+
+                //find all sessions that have expired at least a couple of scanIntervals ago and just delete them
+                upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
+                if (upperBound > 0)
+                {
+                    if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound);
+                    try
+                    {
+                        statement = connection.prepareStatement(_deleteOldExpiredSessions);
+                        statement.setLong(1, upperBound);
+                        int rows = statement.executeUpdate();
+                        if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound);
+                    }
+                    finally
+                    {
+                        if (statement!=null)
+                        {
+                            try { statement.close(); }
+                            catch(Exception e) { LOG.warn(e); }
+                        }
+                    }
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            if (isRunning())    
+                LOG.warn("Problem selecting expired sessions", e);
+            else
+                LOG.ignore(e);
+        }
+        finally
+        {           
+            _lastScavengeTime=System.currentTimeMillis();
+            if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime);
+            if (connection != null)
+            {
+                try
+                {
+                connection.close();
+                }
+                catch (SQLException e)
+                {
+                    LOG.warn(e);
+                }
+            }
+        }
+    }
+    
+    /**
+     * Get rid of sessions and sessionids from sessions that have already expired
+     * @throws Exception
+     */
+    private void cleanExpiredSessions ()
+    {
+        Connection connection = null;
+        PreparedStatement statement = null;
+        Statement sessionsTableStatement = null;
+        Statement sessionIdsTableStatement = null;
+        List<String> expiredSessionIds = new ArrayList<String>();
+        try
+        {     
+            connection = getConnection();
+            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
+            connection.setAutoCommit(false);
+
+            statement = connection.prepareStatement(_selectExpiredSessions);
+            long now = System.currentTimeMillis();
+            if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now);
+
+            statement.setLong(1, now);
+            ResultSet result = statement.executeQuery();
+            while (result.next())
+            {
+                String sessionId = result.getString("sessionId");
+                expiredSessionIds.add(sessionId);
+                if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId); 
+            }
+            
+            sessionsTableStatement = null;
+            sessionIdsTableStatement = null;
+
+            if (!expiredSessionIds.isEmpty())
+            {
+                sessionsTableStatement = connection.createStatement();
+                sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds));
+                sessionIdsTableStatement = connection.createStatement();
+                sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds));
+            }
+            connection.commit();
+
+            synchronized (_sessionIds)
+            {
+                _sessionIds.removeAll(expiredSessionIds); //in case they were in our local cache of session ids
+            }
+        }
+        catch (Exception e)
+        {
+            if (connection != null)
+            {
+                try 
+                { 
+                    LOG.warn("Rolling back clean of expired sessions", e);
+                    connection.rollback();
+                }
+                catch (Exception x) { LOG.warn("Rollback of expired sessions failed", x);}
+            }
+        }
+        finally
+        {
+            if (sessionIdsTableStatement!=null)
+            {
+                try { sessionIdsTableStatement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (sessionsTableStatement!=null)
+            {
+                try { sessionsTableStatement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            try
+            {
+                if (connection != null)
+                    connection.close();
+            }
+            catch (SQLException e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+    
+    
+    /**
+     * 
+     * @param sql
+     * @param connection
+     * @param expiredSessionIds
+     * @throws Exception
+     */
+    private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds)
+    throws Exception
+    {
+        StringBuffer buff = new StringBuffer();
+        buff.append(sql);
+        buff.append("(");
+        Iterator<String> itor = expiredSessionIds.iterator();
+        while (itor.hasNext())
+        {
+            buff.append("'"+(itor.next())+"'");
+            if (itor.hasNext())
+                buff.append(",");
+        }
+        buff.append(")");
+        
+        if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff);
+        return buff.toString();
+    }
+    
+    private void initializeDatabase ()
+    throws Exception
+    {
+        if (_datasource != null)
+            return; //already set up
+        
+        if (_jndiName!=null)
+        {
+            InitialContext ic = new InitialContext();
+            _datasource = (DataSource)ic.lookup(_jndiName);
+        }
+        else if ( _driver != null && _connectionUrl != null )
+        {
+            DriverManager.registerDriver(_driver);
+        }
+        else if (_driverClassName != null && _connectionUrl != null)
+        {
+            Class.forName(_driverClassName);
+        }
+        else
+            throw new IllegalStateException("No database configured for sessions");
+    }
+    
+   
+}
diff --git a/src/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/src/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
new file mode 100644
index 0000000..fc50b77
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/JDBCSessionManager.java
@@ -0,0 +1,1174 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.server.session;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.server.SessionIdManager;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JDBCSessionManager
+ *
+ * SessionManager that persists sessions to a database to enable clustering.
+ *
+ * Session data is persisted to the JettySessions table:
+ *
+ * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
+ * contextPath (of the context owning the session)
+ * sessionId (unique in a context)
+ * lastNode (name of node last handled session)
+ * accessTime (time in milliseconds session was accessed)
+ * lastAccessTime (previous time in milliseconds session was accessed)
+ * createTime (time in milliseconds session created)
+ * cookieTime (time in milliseconds session cookie created)
+ * lastSavedTime (last time in milliseconds session access times were saved)
+ * expiryTime (time in milliseconds that the session is due to expire)
+ * map (attribute map)
+ *
+ * As an optimization, to prevent thrashing the database, we do not persist
+ * the accessTime and lastAccessTime every time the session is accessed. Rather,
+ * we write it out every so often. The frequency is controlled by the saveIntervalSec
+ * field.
+ */
+public class JDBCSessionManager extends AbstractSessionManager
+{
+    private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
+
+    private ConcurrentHashMap<String, AbstractSession> _sessions;
+    protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
+    protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
+
+   
+
+
+    /**
+     * Session
+     *
+     * Session instance.
+     */
+    public class Session extends AbstractSession
+    {
+        private static final long serialVersionUID = 5208464051134226143L;
+        
+        /**
+         * If dirty, session needs to be (re)persisted
+         */
+        private boolean _dirty=false;
+        
+        
+        /**
+         * Time in msec since the epoch that a session cookie was set for this session
+         */
+        private long _cookieSet;
+        
+        
+        /**
+         * Time in msec since the epoch that the session will expire
+         */
+        private long _expiryTime;
+        
+        
+        /**
+         * Time in msec since the epoch that the session was last persisted
+         */
+        private long _lastSaved;
+        
+        
+        /**
+         * Unique identifier of the last node to host the session
+         */
+        private String _lastNode;
+        
+        
+        /**
+         * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        private String _virtualHost;
+        
+        
+        /**
+         * Unique row in db for session
+         */
+        private String _rowId;
+        
+        
+        /**
+         * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
+         */
+        private String _canonicalContext;
+        
+   
+        /**
+         * Session from a request.
+         *
+         * @param request
+         */
+        protected Session (HttpServletRequest request)
+        {
+            super(JDBCSessionManager.this,request);
+            int maxInterval=getMaxInactiveInterval();
+            _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
+            _virtualHost = JDBCSessionManager.getVirtualHost(_context);
+            _canonicalContext = canonicalize(_context.getContextPath());
+            _lastNode = getSessionIdManager().getWorkerName();
+        }
+        
+        
+        /**
+         * Session restored from database
+         * @param sessionId
+         * @param rowId
+         * @param created
+         * @param accessed
+         */
+        protected Session (String sessionId, String rowId, long created, long accessed)
+        {
+            super(JDBCSessionManager.this, created, accessed, sessionId);
+            _rowId = rowId;
+        }
+        
+        
+        protected synchronized String getRowId()
+        {
+            return _rowId;
+        }
+        
+        protected synchronized void setRowId(String rowId)
+        {
+            _rowId = rowId;
+        }
+        
+        public synchronized void setVirtualHost (String vhost)
+        {
+            _virtualHost=vhost;
+        }
+
+        public synchronized String getVirtualHost ()
+        {
+            return _virtualHost;
+        }
+        
+        public synchronized long getLastSaved ()
+        {
+            return _lastSaved;
+        }
+
+        public synchronized void setLastSaved (long time)
+        {
+            _lastSaved=time;
+        }
+
+        public synchronized void setExpiryTime (long time)
+        {
+            _expiryTime=time;
+        }
+
+        public synchronized long getExpiryTime ()
+        {
+            return _expiryTime;
+        }
+        
+
+        public synchronized void setCanonicalContext(String str)
+        {
+            _canonicalContext=str;
+        }
+
+        public synchronized String getCanonicalContext ()
+        {
+            return _canonicalContext;
+        }
+        
+        public void setCookieSet (long ms)
+        {
+            _cookieSet = ms;
+        }
+
+        public synchronized long getCookieSet ()
+        {
+            return _cookieSet;
+        }
+
+        public synchronized void setLastNode (String node)
+        {
+            _lastNode=node;
+        }
+
+        public synchronized String getLastNode ()
+        {
+            return _lastNode;
+        }
+
+        @Override
+        public void setAttribute (String name, Object value)
+        {
+            super.setAttribute(name, value);
+            _dirty=true;
+        }
+
+        @Override
+        public void removeAttribute (String name)
+        {
+            super.removeAttribute(name);
+            _dirty=true;
+        }
+
+        @Override
+        protected void cookieSet()
+        {
+            _cookieSet = getAccessed();
+        }
+
+        /**
+         * Entry to session.
+         * Called by SessionHandler on inbound request and the session already exists in this node's memory.
+         *
+         * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
+         */
+        @Override
+        protected boolean access(long time)
+        {
+            synchronized (this)
+            {
+                if (super.access(time))
+                {
+                    int maxInterval=getMaxInactiveInterval();
+                    _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
+                    return true;
+                }
+                return false;
+            }
+        }
+        
+
+
+        /**
+         * Exit from session
+         * @see org.eclipse.jetty.server.session.AbstractSession#complete()
+         */
+        @Override
+        protected void complete()
+        {
+            synchronized (this)
+            {
+                super.complete();
+                try
+                {
+                    if (isValid())
+                    {
+                        if (_dirty)
+                        {
+                            //The session attributes have changed, write to the db, ensuring
+                            //http passivation/activation listeners called
+                            willPassivate();                      
+                            updateSession(this);
+                            didActivate();
+                        }
+                        else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
+                        {
+                            updateSessionAccessTime(this);
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem persisting changed session data id="+getId(), e);
+                }
+                finally
+                {
+                    _dirty=false;
+                }
+            }
+        }
+
+        @Override
+        protected void timeout() throws IllegalStateException
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Timing out session id="+getClusterId());
+            super.timeout();
+        }
+        
+        @Override
+        public String toString ()
+        {
+            return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
+                            ",created="+getCreationTime()+",accessed="+getAccessed()+
+                            ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
+                            ",lastSaved="+_lastSaved+",expiry="+_expiryTime;
+        }
+    }
+
+
+
+
+    /**
+     * ClassLoadingObjectInputStream
+     *
+     * Used to persist the session attribute map
+     */
+    protected class ClassLoadingObjectInputStream extends ObjectInputStream
+    {
+        public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
+        {
+            super(in);
+        }
+
+        public ClassLoadingObjectInputStream () throws IOException
+        {
+            super();
+        }
+
+        @Override
+        public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
+        {
+            try
+            {
+                return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
+            }
+            catch (ClassNotFoundException e)
+            {
+                return super.resolveClass(cl);
+            }
+        }
+    }
+
+
+    /**
+     * Set the time in seconds which is the interval between
+     * saving the session access time to the database.
+     *
+     * This is an optimization that prevents the database from
+     * being overloaded when a session is accessed very frequently.
+     *
+     * On session exit, if the session attributes have NOT changed,
+     * the time at which we last saved the accessed
+     * time is compared to the current accessed time. If the interval
+     * is at least saveIntervalSecs, then the access time will be
+     * persisted to the database.
+     *
+     * If any session attribute does change, then the attributes and
+     * the accessed time are persisted.
+     *
+     * @param sec
+     */
+    public void setSaveInterval (long sec)
+    {
+        _saveIntervalSec=sec;
+    }
+
+    public long getSaveInterval ()
+    {
+        return _saveIntervalSec;
+    }
+
+
+
+    /**
+     * A method that can be implemented in subclasses to support
+     * distributed caching of sessions. This method will be
+     * called whenever the session is written to the database
+     * because the session data has changed.
+     *
+     * This could be used eg with a JMS backplane to notify nodes
+     * that the session has changed and to delete the session from
+     * the node's cache, and re-read it from the database.
+     * @param session
+     */
+    public void cacheInvalidate (Session session)
+    {
+
+    }
+
+
+    /**
+     * A session has been requested by its id on this node.
+     *
+     * Load the session by id AND context path from the database.
+     * Multiple contexts may share the same session id (due to dispatching)
+     * but they CANNOT share the same contents.
+     *
+     * Check if last node id is my node id, if so, then the session we have
+     * in memory cannot be stale. If another node used the session last, then
+     * we need to refresh from the db.
+     *
+     * NOTE: this method will go to the database, so if you only want to check
+     * for the existence of a Session in memory, use _sessions.get(id) instead.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
+     */
+    @Override
+    public Session getSession(String idInCluster)
+    {
+        Session session = null;
+        Session memSession = (Session)_sessions.get(idInCluster);
+
+        synchronized (this)
+        {
+                //check if we need to reload the session -
+                //as an optimization, don't reload on every access
+                //to reduce the load on the database. This introduces a window of
+                //possibility that the node may decide that the session is local to it,
+                //when the session has actually been live on another node, and then
+                //re-migrated to this node. This should be an extremely rare occurrence,
+                //as load-balancers are generally well-behaved and consistently send
+                //sessions to the same node, changing only iff that node fails.
+                //Session data = null;
+                long now = System.currentTimeMillis();
+                if (LOG.isDebugEnabled())
+                {
+                    if (memSession==null)
+                        LOG.debug("getSession("+idInCluster+"): not in session map,"+
+                                " now="+now+
+                                " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                                " interval="+(_saveIntervalSec * 1000L));
+                    else
+                        LOG.debug("getSession("+idInCluster+"): in session map, "+
+                                " now="+now+
+                                " lastSaved="+(memSession==null?0:memSession._lastSaved)+
+                                " interval="+(_saveIntervalSec * 1000L)+
+                                " lastNode="+memSession._lastNode+
+                                " thisNode="+getSessionIdManager().getWorkerName()+
+                                " difference="+(now - memSession._lastSaved));
+                }
+
+                try
+                {
+                    if (memSession==null)
+                    {
+                        LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
+                        session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                    }
+                    else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
+                    {
+                        LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
+                        session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
+                    }
+                    else
+                    {
+                        LOG.debug("getSession("+idInCluster+"): session in session map");
+                        session = memSession;
+                    }
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Unable to load session "+idInCluster, e);
+                    return null;
+                }
+
+                
+                //If we have a session
+                if (session != null)
+                {
+                    //If the session was last used on a different node, or session doesn't exist on this node
+                    if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
+                    {
+                        //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
+                        if (session._expiryTime <= 0 || session._expiryTime > now)
+                        {
+                            if (LOG.isDebugEnabled()) 
+                                LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
+                            
+                            session.setLastNode(getSessionIdManager().getWorkerName());                            
+                            _sessions.put(idInCluster, session);
+                            
+                            //update in db: if unable to update, session will be scavenged later
+                            try
+                            {
+                                updateSessionNode(session);
+                                session.didActivate();
+                            }
+                            catch (Exception e)
+                            {
+                                LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
+                                return null;
+                            }
+                        }
+                        else
+                        {
+                            LOG.debug("getSession ({}): Session has expired", idInCluster);  
+                            session=null;
+                        }
+
+                    }
+                    else
+                    {
+                       //the session loaded from the db and the one in memory are the same, so keep using the one in memory
+                       session = memSession;
+                       LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
+                    }
+                }
+                else
+                {
+                    //No session in db with matching id and context path.
+                    LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
+                }
+
+                return session;
+        }
+    }
+
+    /**
+     * Get the number of sessions.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
+     */
+    @Override
+    public int getSessions()
+    {
+        int size = 0;
+        synchronized (this)
+        {
+            size = _sessions.size();
+        }
+        return size;
+    }
+
+
+    /**
+     * Start the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
+     */
+    @Override
+    public void doStart() throws Exception
+    {
+        if (_sessionIdManager==null)
+            throw new IllegalStateException("No session id manager defined");
+
+        _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
+        
+        _sessions = new ConcurrentHashMap<String, AbstractSession>();
+
+        super.doStart();
+    }
+
+
+    /**
+     * Stop the session manager.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
+     */
+    @Override
+    public void doStop() throws Exception
+    {
+        _sessions.clear();
+        _sessions = null;
+
+        super.doStop();
+    }
+
+    @Override
+    protected void invalidateSessions()
+    {
+        //Do nothing - we don't want to remove and
+        //invalidate all the sessions because this
+        //method is called from doStop(), and just
+        //because this context is stopping does not
+        //mean that we should remove the session from
+        //any other nodes
+    }
+
+
+    /**
+     * Invalidate a session.
+     *
+     * @param idInCluster
+     */
+    protected void invalidateSession (String idInCluster)
+    {
+        Session session = null;
+        synchronized (this)
+        {
+            session = (Session)_sessions.get(idInCluster);
+        }
+
+        if (session != null)
+        {
+            session.invalidate();
+        }
+    }
+
+    /**
+     * Delete an existing session, both from the in-memory map and
+     * the database.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
+     */
+    @Override
+    protected boolean removeSession(String idInCluster)
+    {
+        synchronized (this)
+        {
+            Session session = (Session)_sessions.remove(idInCluster);
+            try
+            {
+                if (session != null)
+                    deleteSession(session);
+            }
+            catch (Exception e)
+            {
+                LOG.warn("Problem deleting session id="+idInCluster, e);
+            }
+            return session!=null;
+        }
+    }
+
+
+    /**
+     * Add a newly created session to our in-memory list for this node and persist it.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
+     */
+    @Override
+    protected void addSession(AbstractSession session)
+    {
+        if (session==null)
+            return;
+
+        synchronized (this)
+        {
+            _sessions.put(session.getClusterId(), session);
+        }
+
+        //TODO or delay the store until exit out of session? If we crash before we store it
+        //then session data will be lost.
+        try
+        {
+            synchronized (session)
+            {
+                session.willPassivate();
+                storeSession(((JDBCSessionManager.Session)session));
+                session.didActivate();
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Unable to store new session id="+session.getId() , e);
+        }
+    }
+
+
+    /**
+     * Make a new Session.
+     *
+     * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
+     */
+    @Override
+    protected AbstractSession newSession(HttpServletRequest request)
+    {
+        return new Session(request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Remove session from manager
+     * @param session The session to remove
+     * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
+     * {@link SessionIdManager#invalidateAll(String)} should be called.
+     */
+    @Override
+    public void removeSession(AbstractSession session, boolean invalidate)
+    {
+        // Remove session from context and global maps
+        boolean removed = false;
+
+        synchronized (this)
+        {
+            //take this session out of the map of sessions for this context
+            if (getSession(session.getClusterId()) != null)
+            {
+                removed = true;
+                removeSession(session.getClusterId());
+            }
+        }
+
+        if (removed)
+        {
+            // Remove session from all context and global id maps
+            _sessionIdManager.removeSession(session);
+
+            if (invalidate)
+                _sessionIdManager.invalidateAll(session.getClusterId());
+
+            if (invalidate && !_sessionListeners.isEmpty())
+            {
+                HttpSessionEvent event=new HttpSessionEvent(session);
+                for (HttpSessionListener l : _sessionListeners)
+                    l.sessionDestroyed(event);
+            }
+            if (!invalidate)
+            {
+                session.willPassivate();
+            }
+        }
+    }
+
+
+    /**
+     * Expire any Sessions we have in memory matching the list of
+     * expired Session ids.
+     *
+     * @param sessionIds
+     */
+    protected void expire (List<?> sessionIds)
+    {
+        //don't attempt to scavenge if we are shutting down
+        if (isStopping() || isStopped())
+            return;
+
+        //Remove any sessions we already have in memory that match the ids
+        Thread thread=Thread.currentThread();
+        ClassLoader old_loader=thread.getContextClassLoader();
+        ListIterator<?> itor = sessionIds.listIterator();
+
+        try
+        {
+            while (itor.hasNext())
+            {
+                String sessionId = (String)itor.next();
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Expiring session id "+sessionId);
+
+                Session session = (Session)_sessions.get(sessionId);
+                if (session != null)
+                {
+                    session.timeout();
+                    itor.remove();
+                }
+                else
+                {
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Unrecognized session id="+sessionId);
+                }
+            }
+        }
+        catch (Throwable t)
+        {
+            LOG.warn("Problem expiring sessions", t);
+        }
+        finally
+        {
+            thread.setContextClassLoader(old_loader);
+        }
+    }
+
+
+    /**
+     * Load a session from the database
+     * @param id
+     * @return the session data that was loaded
+     * @throws Exception
+     */
+    protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
+    throws Exception
+    {
+        final AtomicReference<Session> _reference = new AtomicReference<Session>();
+        final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
+        Runnable load = new Runnable()
+        {
+            @SuppressWarnings("unchecked")
+            public void run()
+            {
+                Session session = null;
+                Connection connection=null;
+                PreparedStatement statement = null;
+                try
+                {
+                    connection = getConnection();
+                    statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
+                    ResultSet result = statement.executeQuery();
+                    if (result.next())
+                    {                    
+                        session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime"));
+                        session.setCookieSet(result.getLong("cookieTime"));
+                        session.setLastAccessedTime(result.getLong("lastAccessTime"));
+                        session.setLastNode(result.getString("lastNode"));
+                        session.setLastSaved(result.getLong("lastSavedTime"));
+                        session.setExpiryTime(result.getLong("expiryTime"));
+                        session.setCanonicalContext(result.getString("contextPath"));
+                        session.setVirtualHost(result.getString("virtualHost"));
+                                           
+                        InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
+                        ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
+                        Object o = ois.readObject();
+                        session.addAttributes((Map<String,Object>)o);
+                        ois.close();
+
+                        if (LOG.isDebugEnabled())
+                            LOG.debug("LOADED session "+session);
+                    }
+                    _reference.set(session);
+                }
+                catch (Exception e)
+                {
+                    _exception.set(e);
+                }
+                finally
+                {
+                    if (statement!=null)
+                    {
+                        try { statement.close(); }
+                        catch(Exception e) { LOG.warn(e); }
+                    }
+
+                    if (connection!=null)
+                    {
+                        try { connection.close();}
+                        catch(Exception e) { LOG.warn(e); }
+                    }
+                }
+            }
+        };
+
+        if (_context==null)
+            load.run();
+        else
+            _context.getContextHandler().handle(load);
+
+        if (_exception.get()!=null)
+        {
+            //if the session could not be restored, take its id out of the pool of currently-in-use
+            //session ids
+            _jdbcSessionIdMgr.removeSession(id);
+            throw _exception.get();
+        }
+
+        return _reference.get();
+    }
+
+    /**
+     * Insert a session into the database.
+     *
+     * @param data
+     * @throws Exception
+     */
+    protected void storeSession (Session session)
+    throws Exception
+    {
+        if (session==null)
+            return;
+
+        //put into the database
+        Connection connection = getConnection();
+        PreparedStatement statement = null;
+        try
+        {
+            String rowId = calculateRowId(session);
+
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
+            statement.setString(1, rowId); //rowId
+            statement.setString(2, session.getId()); //session id
+            statement.setString(3, session.getCanonicalContext()); //context path
+            statement.setString(4, session.getVirtualHost()); //first vhost
+            statement.setString(5, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(6, session.getAccessed());//accessTime
+            statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(8, session.getCreationTime()); //time created
+            statement.setLong(9, session.getCookieSet());//time cookie was set
+            statement.setLong(10, now); //last saved time
+            statement.setLong(11, session.getExpiryTime());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(session.getAttributeMap());
+            byte[] bytes = baos.toByteArray();
+
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+            statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
+
+            statement.executeUpdate();
+            session.setRowId(rowId); //set it on the in-memory data as well as in db
+            session.setLastSaved(now);
+
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Stored session "+session);
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection!=null)
+                connection.close();
+        }
+    }
+
+
+    /**
+     * Update data on an existing persisted session.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSession (Session data)
+    throws Exception
+    {
+        if (data==null)
+            return;
+
+        Connection connection = getConnection();
+        PreparedStatement statement = null;
+        try
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
+            statement.setString(1, getSessionIdManager().getWorkerName());//my node id
+            statement.setLong(2, data.getAccessed());//accessTime
+            statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime
+            statement.setLong(4, now); //last saved time
+            statement.setLong(5, data.getExpiryTime());
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(baos);
+            oos.writeObject(data.getAttributeMap());
+            byte[] bytes = baos.toByteArray();
+            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+
+            statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob
+            statement.setString(7, data.getRowId()); //rowId
+            statement.executeUpdate();
+
+            data.setLastSaved(now);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Updated session "+data);
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection!=null)
+                connection.close();
+        }
+    }
+
+
+    /**
+     * Update the node on which the session was last seen to be my node.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    protected void updateSessionNode (Session data)
+    throws Exception
+    {
+        String nodeId = getSessionIdManager().getWorkerName();
+        Connection connection = getConnection();
+        PreparedStatement statement = null;
+        try
+        {
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode);
+            statement.setString(1, nodeId);
+            statement.setString(2, data.getRowId());
+            statement.executeUpdate();
+            statement.close();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection!=null)
+                connection.close();
+        }
+    }
+
+    /**
+     * Persist the time the session was last accessed.
+     *
+     * @param data the session
+     * @throws Exception
+     */
+    private void updateSessionAccessTime (Session data)
+    throws Exception
+    {
+        Connection connection = getConnection();
+        PreparedStatement statement = null;
+        try
+        {
+            long now = System.currentTimeMillis();
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
+            statement.setString(1, getSessionIdManager().getWorkerName());
+            statement.setLong(2, data.getAccessed());
+            statement.setLong(3, data.getLastAccessedTime());
+            statement.setLong(4, now);
+            statement.setLong(5, data.getExpiryTime());
+            statement.setString(6, data.getRowId());
+            statement.executeUpdate();
+            data.setLastSaved(now);
+            statement.close();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Updated access time session id="+data.getId());
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection!=null)
+                connection.close();
+        }
+    }
+
+
+
+
+    /**
+     * Delete a session from the database. Should only be called
+     * when the session has been invalidated.
+     *
+     * @param data
+     * @throws Exception
+     */
+    protected void deleteSession (Session data)
+    throws Exception
+    {
+        Connection connection = getConnection();
+        PreparedStatement statement = null;
+        try
+        {
+            connection.setAutoCommit(true);
+            statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession);
+            statement.setString(1, data.getRowId());
+            statement.executeUpdate();
+            if (LOG.isDebugEnabled())
+                LOG.debug("Deleted Session "+data);
+        }
+        finally
+        {
+            if (statement!=null)
+            {
+                try { statement.close(); }
+                catch(Exception e) { LOG.warn(e); }
+            }
+
+            if (connection!=null)
+                connection.close();
+        }
+    }
+
+
+
+    /**
+     * Get a connection from the driver.
+     * @return
+     * @throws SQLException
+     */
+    private Connection getConnection ()
+    throws SQLException
+    {
+        return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
+    }
+
+    /**
+     * Calculate a unique id for this session across the cluster.
+     *
+     * Unique id is composed of: contextpath_virtualhost0_sessionid
+     * @param data
+     * @return
+     */
+    private String calculateRowId (Session data)
+    {
+        String rowId = canonicalize(_context.getContextPath());
+        rowId = rowId + "_" + getVirtualHost(_context);
+        rowId = rowId+"_"+data.getId();
+        return rowId;
+    }
+
+    /**
+     * Get the first virtual host for the context.
+     *
+     * Used to help identify the exact session/contextPath.
+     *
+     * @return 0.0.0.0 if no virtual host is defined
+     */
+    private static String getVirtualHost (ContextHandler.Context context)
+    {
+        String vhost = "0.0.0.0";
+
+        if (context==null)
+            return vhost;
+
+        String [] vhosts = context.getContextHandler().getVirtualHosts();
+        if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
+            return vhost;
+
+        return vhosts[0];
+    }
+
+    /**
+     * Make an acceptable file name from a context path.
+     *
+     * @param path
+     * @return
+     */
+    private static String canonicalize (String path)
+    {
+        if (path==null)
+            return "";
+
+        return path.replace('/', '_').replace('.','_').replace('\\','_');
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/session/SessionHandler.java b/src/java/org/eclipse/jetty/server/session/SessionHandler.java
new file mode 100644
index 0000000..19f5f05
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/SessionHandler.java
@@ -0,0 +1,346 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session;
+
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.EventListener;
+import javax.servlet.DispatcherType;
+import javax.servlet.ServletException;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.SessionManager;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * SessionHandler.
+ */
+public class SessionHandler extends ScopedHandler
+{
+    final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
+
+    public final static EnumSet<SessionTrackingMode> DEFAULT_TRACKING = EnumSet.of(SessionTrackingMode.COOKIE,SessionTrackingMode.URL);
+
+    /* -------------------------------------------------------------- */
+    private SessionManager _sessionManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
+     */
+    public SessionHandler()
+    {
+        this(new HashSessionManager());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param manager
+     *            The session manager
+     */
+    public SessionHandler(SessionManager manager)
+    {
+        setSessionManager(manager);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionManager.
+     */
+    public SessionManager getSessionManager()
+    {
+        return _sessionManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionManager
+     *            The sessionManager to set.
+     */
+    public void setSessionManager(SessionManager sessionManager)
+    {
+        if (isStarted())
+            throw new IllegalStateException();
+        SessionManager old_session_manager = _sessionManager;
+
+        if (getServer() != null)
+            getServer().getContainer().update(this,old_session_manager,sessionManager,"sessionManager",true);
+
+        if (sessionManager != null)
+            sessionManager.setSessionHandler(this);
+
+        _sessionManager = sessionManager;
+
+        if (old_session_manager != null)
+            old_session_manager.setSessionHandler(null);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        Server old_server = getServer();
+        if (old_server != null && old_server != server)
+            old_server.getContainer().update(this,_sessionManager,null,"sessionManager",true);
+        super.setServer(server);
+        if (server != null && server != old_server)
+            server.getContainer().update(this,null,_sessionManager,"sessionManager",true);
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _sessionManager.start();
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        // Destroy sessions before destroying servlets/filters see JETTY-1266
+        _sessionManager.stop();
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        SessionManager old_session_manager = null;
+        HttpSession old_session = null;
+        HttpSession access = null;
+        try
+        {
+            old_session_manager = baseRequest.getSessionManager();
+            old_session = baseRequest.getSession(false);
+
+            if (old_session_manager != _sessionManager)
+            {
+                // new session context
+                baseRequest.setSessionManager(_sessionManager);
+                baseRequest.setSession(null);
+                checkRequestedSessionId(baseRequest,request);
+            }
+
+            // access any existing session
+            HttpSession session = null;
+            if (_sessionManager != null)
+            {
+                session = baseRequest.getSession(false);
+                if (session != null)
+                {
+                    if (session != old_session)
+                    {
+                        access = session;
+                        HttpCookie cookie = _sessionManager.access(session,request.isSecure());
+                        if (cookie != null) // Handle changed ID or max-age refresh
+                            baseRequest.getResponse().addCookie(cookie);
+                    }
+                }
+                else
+                {
+                    session = baseRequest.recoverNewSession(_sessionManager);
+                    if (session != null)
+                        baseRequest.setSession(session);
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("sessionManager=" + _sessionManager);
+                LOG.debug("session=" + session);
+            }
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (_nextScope != null)
+                _nextScope.doScope(target,baseRequest,request,response);
+            else if (_outerScope != null)
+                _outerScope.doHandle(target,baseRequest,request,response);
+            else
+                doHandle(target,baseRequest,request,response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+
+        }
+        finally
+        {
+            if (access != null)
+                _sessionManager.complete(access);
+
+            HttpSession session = baseRequest.getSession(false);
+            if (session != null && old_session == null && session != access)
+                _sessionManager.complete(session);
+
+            if (old_session_manager != null && old_session_manager != _sessionManager)
+            {
+                baseRequest.setSessionManager(old_session_manager);
+                baseRequest.setSession(old_session);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // start manual inline of nextHandle(target,baseRequest,request,response);
+        if (never())
+            nextHandle(target,baseRequest,request,response);
+        else if (_nextScope != null && _nextScope == _handler)
+            _nextScope.doHandle(target,baseRequest,request,response);
+        else if (_handler != null)
+            _handler.handle(target,baseRequest,request,response);
+        // end manual inline
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Look for a requested session ID in cookies and URI parameters
+     *
+     * @param baseRequest
+     * @param request
+     */
+    protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
+    {
+        String requested_session_id = request.getRequestedSessionId();
+
+        SessionManager sessionManager = getSessionManager();
+
+        if (requested_session_id != null && sessionManager != null)
+        {
+            HttpSession session = sessionManager.getHttpSession(requested_session_id);
+            if (session != null && sessionManager.isValid(session))
+                baseRequest.setSession(session);
+            return;
+        }
+        else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
+            return;
+
+        boolean requested_session_id_from_cookie = false;
+        HttpSession session = null;
+
+        // Look for session id cookie
+        if (_sessionManager.isUsingCookies())
+        {
+            Cookie[] cookies = request.getCookies();
+            if (cookies != null && cookies.length > 0)
+            {
+                final String sessionCookie=sessionManager.getSessionCookieConfig().getName();
+                for (int i = 0; i < cookies.length; i++)
+                {
+                    if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
+                    {
+                        requested_session_id = cookies[i].getValue();
+                        requested_session_id_from_cookie = true;
+
+                        LOG.debug("Got Session ID {} from cookie",requested_session_id);
+
+                        if (requested_session_id != null)
+                        {
+                            session = sessionManager.getHttpSession(requested_session_id);
+
+                            if (session != null && sessionManager.isValid(session))
+                            {
+                                break;
+                            }
+                        }
+                        else
+                        {
+                            LOG.warn("null session id from cookie");
+                        }
+                    }
+                }
+            }
+        }
+
+        if (requested_session_id == null || session == null)
+        {
+            String uri = request.getRequestURI();
+
+            String prefix = sessionManager.getSessionIdPathParameterNamePrefix();
+            if (prefix != null)
+            {
+                int s = uri.indexOf(prefix);
+                if (s >= 0)
+                {
+                    s += prefix.length();
+                    int i = s;
+                    while (i < uri.length())
+                    {
+                        char c = uri.charAt(i);
+                        if (c == ';' || c == '#' || c == '?' || c == '/')
+                            break;
+                        i++;
+                    }
+
+                    requested_session_id = uri.substring(s,i);
+                    requested_session_id_from_cookie = false;
+                    session = sessionManager.getHttpSession(requested_session_id);
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Got Session ID {} from URL",requested_session_id);
+                }
+            }
+        }
+
+        baseRequest.setRequestedSessionId(requested_session_id);
+        baseRequest.setRequestedSessionIdFromCookie(requested_session_id != null && requested_session_id_from_cookie);
+        if (session != null && sessionManager.isValid(session))
+            baseRequest.setSession(session);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param listener
+     */
+    public void addEventListener(EventListener listener)
+    {
+        if (_sessionManager != null)
+            _sessionManager.addEventListener(listener);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void clearEventListeners()
+    {
+        if (_sessionManager != null)
+            _sessionManager.clearEventListeners();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/session/jmx/AbstractSessionManagerMBean.java b/src/java/org/eclipse/jetty/server/session/jmx/AbstractSessionManagerMBean.java
new file mode 100644
index 0000000..a9dd4a9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/session/jmx/AbstractSessionManagerMBean.java
@@ -0,0 +1,58 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.session.jmx;
+
+import org.eclipse.jetty.server.handler.AbstractHandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.jmx.AbstractHandlerMBean;
+import org.eclipse.jetty.server.session.AbstractSessionManager;
+import org.eclipse.jetty.server.session.SessionHandler;
+
+public class AbstractSessionManagerMBean extends AbstractHandlerMBean
+{
+    public AbstractSessionManagerMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getObjectContextBasis()
+    {
+        if (_managed != null && _managed instanceof AbstractSessionManager)
+        {
+            AbstractSessionManager manager = (AbstractSessionManager)_managed;
+            
+            String basis = null;
+            SessionHandler handler = manager.getSessionHandler();
+            if (handler != null)
+            {
+                ContextHandler context = 
+                    AbstractHandlerContainer.findContainerOf(handler.getServer(), 
+                                                             ContextHandler.class,
+                                                             handler);
+                if (context != null)
+                    basis = getContextName(context);
+            }
+
+            if (basis != null)
+                return basis;
+        }
+        return super.getObjectContextBasis();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/ssl/ServletSSL.java b/src/java/org/eclipse/jetty/server/ssl/ServletSSL.java
new file mode 100644
index 0000000..6003b68
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ssl/ServletSSL.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.ssl;
+
+/* --------------------------------------------------------------------- */
+/**
+ * Jetty Servlet SSL support utilities.
+ * <p>
+ * A collection of utilities required to support the SSL requirements of the Servlet 2.2 and 2.3
+ * specs.
+ * 
+ * <p>
+ * Used by the SSL listener classes.
+ * 
+ * 
+ */
+public class ServletSSL
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Given the name of a TLS/SSL cipher suite, return an int representing it effective stream
+     * cipher key strength. i.e. How much entropy material is in the key material being fed into the
+     * encryption routines.
+     * 
+     * <p>
+     * This is based on the information on effective key lengths in RFC 2246 - The TLS Protocol
+     * Version 1.0, Appendix C. CipherSuite definitions:
+     * 
+     * <pre>
+     *                         Effective 
+     *     Cipher       Type    Key Bits 
+     * 		       	       
+     *     NULL       * Stream     0     
+     *     IDEA_CBC     Block    128     
+     *     RC2_CBC_40 * Block     40     
+     *     RC4_40     * Stream    40     
+     *     RC4_128      Stream   128     
+     *     DES40_CBC  * Block     40     
+     *     DES_CBC      Block     56     
+     *     3DES_EDE_CBC Block    168     
+     * </pre>
+     * 
+     * @param cipherSuite String name of the TLS cipher suite.
+     * @return int indicating the effective key entropy bit-length.
+     */
+    public static int deduceKeyLength(String cipherSuite)
+    {
+        // Roughly ordered from most common to least common.
+        if (cipherSuite == null)
+            return 0;
+        else if (cipherSuite.indexOf("WITH_AES_256_") >= 0)
+            return 256;
+        else if (cipherSuite.indexOf("WITH_RC4_128_") >= 0)
+            return 128;
+        else if (cipherSuite.indexOf("WITH_AES_128_") >= 0)
+            return 128;
+        else if (cipherSuite.indexOf("WITH_RC4_40_") >= 0)
+            return 40;
+        else if (cipherSuite.indexOf("WITH_3DES_EDE_CBC_") >= 0)
+            return 168;
+        else if (cipherSuite.indexOf("WITH_IDEA_CBC_") >= 0)
+            return 128;
+        else if (cipherSuite.indexOf("WITH_RC2_CBC_40_") >= 0)
+            return 40;
+        else if (cipherSuite.indexOf("WITH_DES40_CBC_") >= 0)
+            return 40;
+        else if (cipherSuite.indexOf("WITH_DES_CBC_") >= 0)
+            return 56;
+        else
+            return 0;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/ssl/SslCertificates.java b/src/java/org/eclipse/jetty/server/ssl/SslCertificates.java
new file mode 100644
index 0000000..655b814
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ssl/SslCertificates.java
@@ -0,0 +1,182 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class SslCertificates
+{
+    private static final Logger LOG = Log.getLogger(SslCertificates.class);
+
+    /**
+     * The name of the SSLSession attribute that will contain any cached information.
+     */
+    static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
+
+    public static X509Certificate[] getCertChain(SSLSession sslSession)
+    {
+        try
+        {
+            javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain();
+            if (javaxCerts==null||javaxCerts.length==0)
+                return null;
+
+            int length=javaxCerts.length;
+            X509Certificate[] javaCerts=new X509Certificate[length];
+
+            java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
+            for (int i=0; i<length; i++)
+            {
+                byte bytes[]=javaxCerts[i].getEncoded();
+                ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
+                javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
+            }
+
+            return javaCerts;
+        }
+        catch (SSLPeerUnverifiedException pue)
+        {
+            return null;
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            return null;
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Allow the Listener a chance to customise the request. before the server
+     * does its stuff. <br>
+     * This allows the required attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+     * String (since Servlet Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+     * String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type
+     * X509Certificate, the order of this array is defined as being in ascending
+     * order of trust. The first certificate in the chain is the one set by the
+     * client, the next is the one used to authenticate the first, and so on.
+     * </li>
+     * </ul>
+     * 
+     * @param endpoint
+     *                The Socket the request arrived on. This should be a
+     *                {@link SocketEndPoint} wrapping a {@link SSLSocket}.
+     * @param request
+     *                HttpRequest to be customised.
+     */
+    public static void customize(SSLSession sslSession, EndPoint endpoint, Request request) throws IOException
+    {
+        request.setScheme(HttpSchemes.HTTPS);
+
+        try
+        {
+            String cipherSuite=sslSession.getCipherSuite();
+            Integer keySize;
+            X509Certificate[] certs;
+            String idStr;
+
+            CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
+            if (cachedInfo!=null)
+            {
+                keySize=cachedInfo.getKeySize();
+                certs=cachedInfo.getCerts();
+                idStr=cachedInfo.getIdStr();
+            }
+            else
+            {
+                keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite));
+                certs=SslCertificates.getCertChain(sslSession);
+                byte[] bytes = sslSession.getId();
+                idStr = TypeUtil.toHexString(bytes);
+                cachedInfo=new CachedInfo(keySize,certs,idStr);
+                sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
+            }
+
+            if (certs!=null)
+                request.setAttribute("javax.servlet.request.X509Certificate",certs);
+
+            request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
+            request.setAttribute("javax.servlet.request.key_size",keySize);
+            request.setAttribute("javax.servlet.request.ssl_session_id", idStr);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Simple bundle of information that is cached in the SSLSession. Stores the
+     * effective keySize and the client certificate chain.
+     */
+    private static class CachedInfo
+    {
+        private final X509Certificate[] _certs;
+        private final Integer _keySize;
+        private final String _idStr;
+
+        CachedInfo(Integer keySize, X509Certificate[] certs,String idStr)
+        {
+            this._keySize=keySize;
+            this._certs=certs;
+            this._idStr=idStr;
+        }
+
+        X509Certificate[] getCerts()
+        {
+            return _certs;
+        }
+
+        Integer getKeySize()
+        {
+            return _keySize;
+        }
+        
+        String getIdStr()
+        {
+            return _idStr;
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/server/ssl/SslConnector.java b/src/java/org/eclipse/jetty/server/ssl/SslConnector.java
new file mode 100644
index 0000000..88aed2a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ssl/SslConnector.java
@@ -0,0 +1,348 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.ssl;
+
+import java.io.File;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+
+/* ------------------------------------------------------------ */
+/** The interface for SSL connectors and their configuration methods.
+ * 
+ */
+public interface SslConnector extends Connector
+{
+    @Deprecated
+    public static final String DEFAULT_KEYSTORE_ALGORITHM=(Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+    @Deprecated
+    public static final String DEFAULT_TRUSTSTORE_ALGORITHM=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+    /** Default value for the keystore location path. @deprecated */
+    @Deprecated
+    public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator + ".keystore";
+    
+    /** String name of key password property. @deprecated */
+    @Deprecated
+    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+    
+    /** String name of keystore password property. @deprecated */
+    @Deprecated
+    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the instance of SslContextFactory associated with the connector
+     */
+    public SslContextFactory getSslContextFactory();
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of Ciphersuite names to exclude from 
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String[] getExcludeCipherSuites();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cipherSuites The array of Ciphersuite names to exclude from 
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setExcludeCipherSuites(String[] cipherSuites);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of Ciphersuite names to include in
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String[] getIncludeCipherSuites();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cipherSuites The array of Ciphersuite names to include in 
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setIncludeCipherSuites(String[] cipherSuites);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password The password for the key store
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setPassword(String password);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password The password for the trust store
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setTrustPassword(String password);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password The password (if any) for the specific key within 
+     * the key store
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setKeyPassword(String password);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getProtocol();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocol The SSL protocol (default "TLS") passed to {@link SSLContext#getInstance(String, String)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setProtocol(String protocol);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keystore The file or URL of the SSL Key store.
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setKeystore(String keystore);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file or URL of the SSL Key store.
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getKeystore();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the key store (default "JKS")
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getKeystoreType();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public abstract boolean getNeedClientAuth();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public abstract boolean getWantClientAuth();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param needClientAuth True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setNeedClientAuth(boolean needClientAuth);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param wantClientAuth True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setWantClientAuth(boolean wantClientAuth);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keystoreType The type of the key store (default "JKS")
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setKeystoreType(String keystoreType);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSL provider name, which if set is passed to 
+     * {@link SSLContext#getInstance(String, String)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getProvider();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name, which if set is passed to 
+     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom}
+     * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getSecureRandomAlgorithm();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getSslKeyManagerFactoryAlgorithm();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getSslTrustManagerFactoryAlgorithm();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file name or URL of the trust store location
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getTruststore();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the trust store (default "JKS")
+     * @deprecated
+     */
+    @Deprecated
+    public abstract String getTruststoreType();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param provider The SSL provider name, which if set is passed to 
+     * {@link SSLContext#getInstance(String, String)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setProvider(String provider);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm The algorithm name, which if set is passed to 
+     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom}
+     * instance passed to {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setSecureRandomAlgorithm(String algorithm);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm The algorithm name (default "SunX509") used by 
+     * the {@link KeyManagerFactory}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setSslKeyManagerFactoryAlgorithm(String algorithm);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setSslTrustManagerFactoryAlgorithm(String algorithm);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param truststore The file name or URL of the trust store location
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setTruststore(String truststore);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param truststoreType The type of the trust store (default "JKS")
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setTruststoreType(String truststoreType);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sslContext Set a preconfigured SSLContext
+     * @deprecated
+     */
+    @Deprecated
+    public abstract void setSslContext(SSLContext sslContext);
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSLContext
+     * @deprecated
+     */
+    @Deprecated
+    public abstract SSLContext getSslContext();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL re-negotiation is allowed (default false)
+     * @deprecated
+     */
+    @Deprecated
+    public boolean isAllowRenegotiate();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
+     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
+     * does not have CVE-2009-3555 fixed, then re-negotiation should 
+     * not be allowed.
+     * @param allowRenegotiate true if re-negotiation is allowed (default false)
+     * @deprecated
+     */
+    @Deprecated
+    public void setAllowRenegotiate(boolean allowRenegotiate);
+}
diff --git a/src/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java b/src/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
new file mode 100644
index 0000000..833a68f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ssl/SslSelectChannelConnector.java
@@ -0,0 +1,653 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.ssl;
+
+import java.io.IOException;
+import java.nio.channels.SocketChannel;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.io.BuffersFactory;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/* ------------------------------------------------------------ */
+/**
+ * SslSelectChannelConnector.
+ *
+ * @org.apache.xbean.XBean element="sslConnector" description="Creates an NIO ssl connector"
+ */
+public class SslSelectChannelConnector extends SelectChannelConnector implements SslConnector
+{
+    private final SslContextFactory _sslContextFactory;
+    private Buffers _sslBuffers;
+
+    /* ------------------------------------------------------------ */
+    public SslSelectChannelConnector()
+    {
+        this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
+        setSoLingerTime(30000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Construct with explicit SslContextFactory.
+     * The SslContextFactory passed is added via {@link #addBean(Object)} so that 
+     * it's lifecycle may be managed with {@link AggregateLifeCycle}.
+     * @param sslContextFactory
+     */
+    public SslSelectChannelConnector(SslContextFactory sslContextFactory)
+    {
+        _sslContextFactory = sslContextFactory;
+        addBean(_sslContextFactory);
+        setUseDirectBuffers(false);
+        setSoLingerTime(30000);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Allow the Listener a chance to customise the request. before the server
+     * does its stuff. <br>
+     * This allows the required attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_session_id" of type
+     * String (since Servlet Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type
+     * String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type
+     * X509Certificate, the order of this array is defined as being in ascending
+     * order of trust. The first certificate in the chain is the one set by the
+     * client, the next is the one used to authenticate the first, and so on.
+     * </li>
+     * </ul>
+     *
+     * @param endpoint
+     *                The Socket the request arrived on. This should be a
+     *                {@link SocketEndPoint} wrapping a {@link SSLSocket}.
+     * @param request
+     *                HttpRequest to be customised.
+     */
+    @Override
+    public void customize(EndPoint endpoint, Request request) throws IOException
+    {
+        request.setScheme(HttpSchemes.HTTPS);
+        super.customize(endpoint,request);
+
+        SslConnection.SslEndPoint sslEndpoint=(SslConnection.SslEndPoint)endpoint;
+        SSLEngine sslEngine=sslEndpoint.getSslEngine();
+        SSLSession sslSession=sslEngine.getSession();
+
+        SslCertificates.customize(sslSession,endpoint,request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL re-negotiation is allowed (default false)
+     * @deprecated
+     */
+    @Deprecated
+    public boolean isAllowRenegotiate()
+    {
+        return _sslContextFactory.isAllowRenegotiate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
+     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
+     * does not have CVE-2009-3555 fixed, then re-negotiation should
+     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
+     * of renegotiate in u19 and with RFC5746 in u22.
+     * @param allowRenegotiate true if re-negotiation is allowed (default false)
+     * @deprecated
+     */
+    @Deprecated
+    public void setAllowRenegotiate(boolean allowRenegotiate)
+    {
+        _sslContextFactory.setAllowRenegotiate(allowRenegotiate);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
+     * @deprecated
+     */
+    @Deprecated
+    public String[] getExcludeCipherSuites()
+    {
+        return _sslContextFactory.getExcludeCipherSuites();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
+     * @deprecated
+     */
+    @Deprecated
+    public void setExcludeCipherSuites(String[] cipherSuites)
+    {
+        _sslContextFactory.setExcludeCipherSuites(cipherSuites);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
+     * @deprecated
+     */
+    @Deprecated
+    public String[] getIncludeCipherSuites()
+    {
+        return _sslContextFactory.getIncludeCipherSuites();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
+     * @deprecated
+     */
+    @Deprecated
+    public void setIncludeCipherSuites(String[] cipherSuites)
+    {
+        _sslContextFactory.setIncludeCipherSuites(cipherSuites);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setPassword(String password)
+    {
+        _sslContextFactory.setKeyStorePassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTrustPassword(String password)
+    {
+        _sslContextFactory.setTrustStorePassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeyPassword(String password)
+    {
+        _sslContextFactory.setKeyManagerPassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unsupported.
+     *
+     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
+     * @deprecated
+     */
+    @Deprecated
+    public String getAlgorithm()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unsupported.
+     *
+     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
+     * @deprecated
+     */
+    @Deprecated
+    public void setAlgorithm(String algorithm)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
+     * @deprecated
+     */
+    @Deprecated
+    public String getProtocol()
+    {
+        return _sslContextFactory.getProtocol();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setProtocol(String protocol)
+    {
+        _sslContextFactory.setProtocol(protocol);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystore(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeystore(String keystore)
+    {
+        _sslContextFactory.setKeyStorePath(keystore);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
+     * @deprecated
+     */
+    @Deprecated
+    public String getKeystore()
+    {
+        return _sslContextFactory.getKeyStorePath();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
+     * @deprecated
+     */
+    @Deprecated
+    public String getKeystoreType()
+    {
+        return _sslContextFactory.getKeyStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getNeedClientAuth()
+    {
+        return _sslContextFactory.getNeedClientAuth();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getWantClientAuth()
+    {
+        return _sslContextFactory.getWantClientAuth();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setNeedClientAuth(boolean)
+     * @deprecated
+     */
+    @Deprecated
+    public void setNeedClientAuth(boolean needClientAuth)
+    {
+        _sslContextFactory.setNeedClientAuth(needClientAuth);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setWantClientAuth(boolean)
+     * @deprecated
+     */
+    @Deprecated
+    public void setWantClientAuth(boolean wantClientAuth)
+    {
+        _sslContextFactory.setWantClientAuth(wantClientAuth);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeystoreType(String keystoreType)
+    {
+        _sslContextFactory.setKeyStoreType(keystoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
+     * @deprecated
+     */
+    @Deprecated
+    public String getProvider()
+    {
+        return _sslContextFactory.getProvider();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSecureRandomAlgorithm()
+    {
+        return _sslContextFactory.getSecureRandomAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSslKeyManagerFactoryAlgorithm()
+    {
+        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSslTrustManagerFactoryAlgorithm()
+    {
+        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
+     * @deprecated
+     */
+    @Deprecated
+    public String getTruststore()
+    {
+        return _sslContextFactory.getTrustStore();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
+     * @deprecated
+     */
+    @Deprecated
+    public String getTruststoreType()
+    {
+        return _sslContextFactory.getTrustStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setProvider(String provider)
+    {
+        _sslContextFactory.setProvider(provider);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSecureRandomAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setSecureRandomAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslTrustManagerFactoryAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTruststore(String truststore)
+    {
+        _sslContextFactory.setTrustStore(truststore);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTruststoreType(String truststoreType)
+    {
+        _sslContextFactory.setTrustStoreType(truststoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslContext(SSLContext sslContext)
+    {
+        _sslContextFactory.setSslContext(sslContext);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
+     * @deprecated
+     */
+    @Deprecated
+    public SSLContext getSslContext()
+    {
+        return _sslContextFactory.getSslContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
+     */
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * By default, we're confidential, given we speak SSL. But, if we've been
+     * told about an confidential port, and said port is not our port, then
+     * we're not. This allows separation of listeners providing INTEGRAL versus
+     * CONFIDENTIAL constraints, such as one SSL listener configured to require
+     * client certs providing CONFIDENTIAL, whereas another SSL listener not
+     * requiring client certs providing mere INTEGRAL constraints.
+     */
+    @Override
+    public boolean isConfidential(Request request)
+    {
+        final int confidentialPort=getConfidentialPort();
+        return confidentialPort==0||confidentialPort==request.getServerPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * By default, we're integral, given we speak SSL. But, if we've been told
+     * about an integral port, and said port is not our port, then we're not.
+     * This allows separation of listeners providing INTEGRAL versus
+     * CONFIDENTIAL constraints, such as one SSL listener configured to require
+     * client certs providing CONFIDENTIAL, whereas another SSL listener not
+     * requiring client certs providing mere INTEGRAL constraints.
+     */
+    @Override
+    public boolean isIntegral(Request request)
+    {
+        final int integralPort=getIntegralPort();
+        return integralPort==0||integralPort==request.getServerPort();
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    protected AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint)
+    {
+        try
+        {
+            SSLEngine engine = createSSLEngine(channel);
+            SslConnection connection = newSslConnection(endpoint, engine);
+            AsyncConnection delegate = newPlainConnection(channel, connection.getSslEndPoint());
+            connection.getSslEndPoint().setConnection(delegate);
+            connection.setAllowRenegotiate(_sslContextFactory.isAllowRenegotiate());
+            return connection;
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeIOException(e);
+        }
+    }
+
+    protected AsyncConnection newPlainConnection(SocketChannel channel, AsyncEndPoint endPoint)
+    {
+        return super.newConnection(channel, endPoint);
+    }
+
+    protected SslConnection newSslConnection(AsyncEndPoint endpoint, SSLEngine engine)
+    {
+        return new SslConnection(engine, endpoint);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param channel A channel which if passed is used as to extract remote
+     * host and port for the purposes of SSL session caching
+     * @return A SSLEngine for a new or cached SSL Session
+     * @throws IOException if the SSLEngine cannot be created
+     */
+    protected SSLEngine createSSLEngine(SocketChannel channel) throws IOException
+    {
+        SSLEngine engine;
+        if (channel != null)
+        {
+            String peerHost = channel.socket().getInetAddress().getHostAddress();
+            int peerPort = channel.socket().getPort();
+            engine = _sslContextFactory.newSslEngine(peerHost, peerPort);
+        }
+        else
+        {
+            engine = _sslContextFactory.newSslEngine();
+        }
+
+        engine.setUseClientMode(false);
+        return engine;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _sslContextFactory.checkKeyStore();
+        _sslContextFactory.start();
+
+        SSLEngine sslEngine = _sslContextFactory.newSslEngine();
+
+        sslEngine.setUseClientMode(false);
+
+        SSLSession sslSession = sslEngine.getSession();
+
+        _sslBuffers = BuffersFactory.newBuffers(
+                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(),
+                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,sslSession.getApplicationBufferSize(),
+                getUseDirectBuffers()?Type.DIRECT:Type.INDIRECT,getMaxBuffers()
+        );
+
+        if (getRequestHeaderSize()<sslSession.getApplicationBufferSize())
+            setRequestHeaderSize(sslSession.getApplicationBufferSize());
+        if (getRequestBufferSize()<sslSession.getApplicationBufferSize())
+            setRequestBufferSize(sslSession.getApplicationBufferSize());
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.nio.SelectChannelConnector#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _sslBuffers=null;
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return SSL buffers
+     */
+    public Buffers getSslBuffers()
+    {
+        return _sslBuffers;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java b/src/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
new file mode 100644
index 0000000..2b94467
--- /dev/null
+++ b/src/java/org/eclipse/jetty/server/ssl/SslSocketConnector.java
@@ -0,0 +1,712 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.server.ssl;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import javax.net.ssl.HandshakeCompletedEvent;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.io.bio.SocketEndPoint;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/* ------------------------------------------------------------ */
+/**
+ * SSL Socket Connector.
+ *
+ * This specialization of SocketConnector is an abstract listener that can be used as the basis for a
+ * specific JSSE listener.
+ *
+ * The original of this class was heavily based on the work from Court Demas, which in turn is
+ * based on the work from Forge Research. Since JSSE, this class has evolved significantly from
+ * that early work.
+ *
+ * @org.apache.xbean.XBean element="sslSocketConnector" description="Creates an ssl socket connector"
+ *
+ *
+ */
+public class SslSocketConnector extends SocketConnector  implements SslConnector
+{
+    private static final Logger LOG = Log.getLogger(SslSocketConnector.class);
+
+    private final SslContextFactory _sslContextFactory;
+    private int _handshakeTimeout = 0; //0 means use maxIdleTime
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public SslSocketConnector()
+    {
+        this(new SslContextFactory(SslContextFactory.DEFAULT_KEYSTORE_PATH));
+        setSoLingerTime(30000);
+    }
+
+    /* ------------------------------------------------------------ */
+    public SslSocketConnector(SslContextFactory sslContextFactory)
+    {
+        _sslContextFactory = sslContextFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL re-negotiation is allowed (default false)
+     */
+    public boolean isAllowRenegotiate()
+    {
+        return _sslContextFactory.isAllowRenegotiate();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
+     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
+     * does not have CVE-2009-3555 fixed, then re-negotiation should
+     * not be allowed.
+     * @param allowRenegotiate true if re-negotiation is allowed (default false)
+     */
+    public void setAllowRenegotiate(boolean allowRenegotiate)
+    {
+        _sslContextFactory.setAllowRenegotiate(allowRenegotiate);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void accept(int acceptorID)
+        throws IOException, InterruptedException
+    {
+        Socket socket = _serverSocket.accept();
+        configure(socket);
+
+        ConnectorEndPoint connection=new SslConnectorEndPoint(socket);
+        connection.dispatch();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void configure(Socket socket)
+        throws IOException
+    {
+        super.configure(socket);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
+     * This allows the required attributes to be set for SSL requests. <br>
+     * The requirements of the Servlet specs are:
+     * <ul>
+     * <li> an attribute named "javax.servlet.request.ssl_id" of type String (since Spec 3.0).</li>
+     * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
+     * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
+     * <li> an attribute named "javax.servlet.request.X509Certificate" of type
+     * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
+     * the order of this array is defined as being in ascending order of trust. The first
+     * certificate in the chain is the one set by the client, the next is the one used to
+     * authenticate the first, and so on. </li>
+     * </ul>
+     *
+     * @param endpoint The Socket the request arrived on.
+     *        This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
+     * @param request HttpRequest to be customised.
+     */
+    @Override
+    public void customize(EndPoint endpoint, Request request)
+        throws IOException
+    {
+        super.customize(endpoint, request);
+        request.setScheme(HttpSchemes.HTTPS);
+
+        SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
+        SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
+        SSLSession sslSession = sslSocket.getSession();
+
+        SslCertificates.customize(sslSession,endpoint,request);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getExcludeCipherSuites()
+     * @deprecated
+     */
+    @Deprecated
+    public String[] getExcludeCipherSuites() {
+        return _sslContextFactory.getExcludeCipherSuites();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getIncludeCipherSuites()
+     * @deprecated
+     */
+    @Deprecated
+    public String[] getIncludeCipherSuites()
+    {
+        return _sslContextFactory.getIncludeCipherSuites();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystore()
+     * @deprecated
+     */
+    @Deprecated
+    public String getKeystore()
+    {
+        return _sslContextFactory.getKeyStorePath();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getKeystoreType()
+     * @deprecated
+     */
+    @Deprecated
+    public String getKeystoreType()
+    {
+        return _sslContextFactory.getKeyStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getNeedClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getNeedClientAuth()
+    {
+        return _sslContextFactory.getNeedClientAuth();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getProtocol()
+     * @deprecated
+     */
+    @Deprecated
+    public String getProtocol()
+    {
+        return _sslContextFactory.getProtocol();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getProvider()
+     * @deprecated
+     */
+    @Deprecated
+    public String getProvider() {
+	return _sslContextFactory.getProvider();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSecureRandomAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSecureRandomAlgorithm()
+    {
+        return _sslContextFactory.getSecureRandomAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslKeyManagerFactoryAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSslKeyManagerFactoryAlgorithm()
+    {
+        return _sslContextFactory.getSslKeyManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslTrustManagerFactoryAlgorithm()
+     * @deprecated
+     */
+    @Deprecated
+    public String getSslTrustManagerFactoryAlgorithm()
+    {
+        return _sslContextFactory.getTrustManagerFactoryAlgorithm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststore()
+     * @deprecated
+     */
+    @Deprecated
+    public String getTruststore()
+    {
+        return _sslContextFactory.getTrustStore();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getSslContextFactory()
+     */
+//    @Override
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getTruststoreType()
+     * @deprecated
+     */
+    @Deprecated
+    public String getTruststoreType()
+    {
+        return _sslContextFactory.getTrustStoreType();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#getWantClientAuth()
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getWantClientAuth()
+    {
+        return _sslContextFactory.getWantClientAuth();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * By default, we're confidential, given we speak SSL. But, if we've been told about an
+     * confidential port, and said port is not our port, then we're not. This allows separation of
+     * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
+     * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
+     * requiring client certs providing mere INTEGRAL constraints.
+     */
+    @Override
+    public boolean isConfidential(Request request)
+    {
+        final int confidentialPort = getConfidentialPort();
+        return confidentialPort == 0 || confidentialPort == request.getServerPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * By default, we're integral, given we speak SSL. But, if we've been told about an integral
+     * port, and said port is not our port, then we're not. This allows separation of listeners
+     * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
+     * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
+     * client certs providing mere INTEGRAL constraints.
+     */
+    @Override
+    public boolean isIntegral(Request request)
+    {
+        final int integralPort = getIntegralPort();
+        return integralPort == 0 || integralPort == request.getServerPort();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void open() throws IOException
+    {
+        _sslContextFactory.checkKeyStore();
+        try
+        {
+            _sslContextFactory.start();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeIOException(e);
+        }
+        super.open();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        _sslContextFactory.checkKeyStore();
+        _sslContextFactory.start();
+
+        super.doStart();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.bio.SocketConnector#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _sslContextFactory.stop();
+
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param host The host name that this server should listen on
+     * @param port the port that this server should listen on
+     * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
+     * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
+     * settings as per the current configuration of this connector.
+     * @see #setWantClientAuth(boolean)
+     * @see #setNeedClientAuth(boolean)
+     * @exception IOException
+     */
+    @Override
+    protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
+    {
+       return _sslContextFactory.newSslServerSocket(host,port,backlog);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setExcludeCipherSuites(java.lang.String[])
+     * @deprecated
+     */
+    @Deprecated
+    public void setExcludeCipherSuites(String[] cipherSuites)
+    {
+        _sslContextFactory.setExcludeCipherSuites(cipherSuites);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setIncludeCipherSuites(java.lang.String[])
+     * @deprecated
+     */
+    @Deprecated
+    public void setIncludeCipherSuites(String[] cipherSuites)
+    {
+        _sslContextFactory.setIncludeCipherSuites(cipherSuites);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeyPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeyPassword(String password)
+    {
+        _sslContextFactory.setKeyManagerPassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keystore The resource path to the keystore, or null for built in keystores.
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeystore(String keystore)
+    {
+        _sslContextFactory.setKeyStorePath(keystore);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setKeystoreType(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setKeystoreType(String keystoreType)
+    {
+        _sslContextFactory.setKeyStoreType(keystoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the value of the needClientAuth property
+     *
+     * @param needClientAuth true iff we require client certificate authentication.
+     * @deprecated
+     */
+    @Deprecated
+    public void setNeedClientAuth(boolean needClientAuth)
+    {
+        _sslContextFactory.setNeedClientAuth(needClientAuth);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setPassword(String password)
+    {
+        _sslContextFactory.setKeyStorePassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTrustPassword(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTrustPassword(String password)
+    {
+        _sslContextFactory.setTrustStorePassword(password);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setProtocol(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setProtocol(String protocol)
+    {
+        _sslContextFactory.setProtocol(protocol);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setProvider(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setProvider(String provider) {
+        _sslContextFactory.setProvider(provider);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSecureRandomAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSecureRandomAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setSecureRandomAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslKeyManagerFactoryAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setSslKeyManagerFactoryAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslTrustManagerFactoryAlgorithm(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslTrustManagerFactoryAlgorithm(String algorithm)
+    {
+        _sslContextFactory.setTrustManagerFactoryAlgorithm(algorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststore(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTruststore(String truststore)
+    {
+        _sslContextFactory.setTrustStore(truststore);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setTruststoreType(java.lang.String)
+     * @deprecated
+     */
+    @Deprecated
+    public void setTruststoreType(String truststoreType)
+    {
+        _sslContextFactory.setTrustStoreType(truststoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
+     * @deprecated
+     */
+    @Deprecated
+    public void setSslContext(SSLContext sslContext)
+    {
+        _sslContextFactory.setSslContext(sslContext);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.ssl.SslConnector#setSslContext(javax.net.ssl.SSLContext)
+     * @deprecated
+     */
+    @Deprecated
+    public SSLContext getSslContext()
+    {
+        return _sslContextFactory.getSslContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the value of the _wantClientAuth property. This property is used
+     * internally when opening server sockets.
+     *
+     * @param wantClientAuth true if we want client certificate authentication.
+     * @see SSLServerSocket#setWantClientAuth
+     * @deprecated
+     */
+    @Deprecated
+    public void setWantClientAuth(boolean wantClientAuth)
+    {
+        _sslContextFactory.setWantClientAuth(wantClientAuth);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the time in milliseconds for so_timeout during ssl handshaking
+     * @param msec a non-zero value will be used to set so_timeout during
+     * ssl handshakes. A zero value means the maxIdleTime is used instead.
+     */
+    public void setHandshakeTimeout (int msec)
+    {
+        _handshakeTimeout = msec;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public int getHandshakeTimeout ()
+    {
+        return _handshakeTimeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    public class SslConnectorEndPoint extends ConnectorEndPoint
+    {
+        public SslConnectorEndPoint(Socket socket) throws IOException
+        {
+            super(socket);
+        }
+
+        @Override
+        public void shutdownOutput() throws IOException
+        {
+            close();
+        }
+
+        @Override
+        public void shutdownInput() throws IOException
+        {
+            close();
+        }
+
+        @Override
+        public void run()
+        {
+            try
+            {
+                int handshakeTimeout = getHandshakeTimeout();
+                int oldTimeout = _socket.getSoTimeout();
+                if (handshakeTimeout > 0)
+                    _socket.setSoTimeout(handshakeTimeout);
+
+                final SSLSocket ssl=(SSLSocket)_socket;
+                ssl.addHandshakeCompletedListener(new HandshakeCompletedListener()
+                {
+                    boolean handshook=false;
+                    public void handshakeCompleted(HandshakeCompletedEvent event)
+                    {
+                        if (handshook)
+                        {
+                            if (!_sslContextFactory.isAllowRenegotiate())
+                            {
+                                LOG.warn("SSL renegotiate denied: "+ssl);
+                                try{ssl.close();}catch(IOException e){LOG.warn(e);}
+                            }
+                        }
+                        else
+                            handshook=true;
+                    }
+                });
+                ssl.startHandshake();
+
+                if (handshakeTimeout>0)
+                    _socket.setSoTimeout(oldTimeout);
+
+                super.run();
+            }
+            catch (SSLException e)
+            {
+                LOG.debug(e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+            catch (IOException e)
+            {
+                LOG.debug(e);
+                try{close();}
+                catch(IOException e2){LOG.ignore(e2);}
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unsupported.
+     *
+     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
+     * @deprecated
+     */
+    @Deprecated
+    public String getAlgorithm()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unsupported.
+     *
+     * TODO: we should remove this as it is no longer an overridden method from SslConnector (like it was in the past)
+     * @deprecated
+     */
+    @Deprecated
+    public void setAlgorithm(String algorithm)
+    {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/DefaultServlet.java b/src/java/org/eclipse/jetty/servlet/DefaultServlet.java
new file mode 100644
index 0000000..6707ab0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/DefaultServlet.java
@@ -0,0 +1,1134 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpContent;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.WriterOutputStream;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.HttpOutput;
+import org.eclipse.jetty.server.InclusiveByteRange;
+import org.eclipse.jetty.server.ResourceCache;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.nio.NIOConnector;
+import org.eclipse.jetty.server.ssl.SslConnector;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiPartOutputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.FileResource;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+
+
+
+/* ------------------------------------------------------------ */
+/** The default servlet.
+ * This servlet, normally mapped to /, provides the handling for static
+ * content, OPTION and TRACE methods for the context.
+ * The following initParameters are supported, these can be set either
+ * on the servlet itself or as ServletContext initParameters with a prefix
+ * of org.eclipse.jetty.servlet.Default. :
+ * <PRE>
+ *  acceptRanges      If true, range requests and responses are
+ *                    supported
+ *
+ *  dirAllowed        If true, directory listings are returned if no
+ *                    welcome file is found. Else 403 Forbidden.
+ *
+ *  welcomeServlets   If true, attempt to dispatch to welcome files
+ *                    that are servlets, but only after no matching static
+ *                    resources could be found. If false, then a welcome
+ *                    file must exist on disk. If "exact", then exact
+ *                    servlet matches are supported without an existing file.
+ *                    Default is true.
+ *
+ *                    This must be false if you want directory listings,
+ *                    but have index.jsp in your welcome file list.
+ *
+ *  redirectWelcome   If true, welcome files are redirected rather than
+ *                    forwarded to.
+ *
+ *  gzip              If set to true, then static content will be served as
+ *                    gzip content encoded if a matching resource is
+ *                    found ending with ".gz"
+ *
+ *  resourceBase      Set to replace the context resource base
+ *
+ *  resourceCache     If set, this is a context attribute name, which the servlet 
+ *                    will use to look for a shared ResourceCache instance. 
+ *                        
+ *  relativeResourceBase
+ *                    Set with a pathname relative to the base of the
+ *                    servlet context root. Useful for only serving static content out
+ *                    of only specific subdirectories.
+ *
+ *  pathInfoOnly      If true, only the path info will be applied to the resourceBase 
+ *                        
+ *  stylesheet	      Set with the location of an optional stylesheet that will be used
+ *                    to decorate the directory listing html.
+ *
+ *  aliases           If True, aliases of resources are allowed (eg. symbolic
+ *                    links and caps variations). May bypass security constraints.
+ *                    
+ *  etags             If True, weak etags will be handled.
+ *
+ *  maxCacheSize      The maximum total size of the cache or 0 for no cache.
+ *  maxCachedFileSize The maximum size of a file to cache
+ *  maxCachedFiles    The maximum number of files to cache
+ *
+ *  useFileMappedBuffer
+ *                    If set to true, it will use mapped file buffer to serve static content
+ *                    when using NIO connector. Setting this value to false means that
+ *                    a direct buffer will be used instead of a mapped file buffer.
+ *                    By default, this is set to true.
+ *
+ *  cacheControl      If set, all static content will have this value set as the cache-control
+ *                    header.
+ *
+ *
+ * </PRE>
+ *
+ *
+ *
+ *
+ */
+public class DefaultServlet extends HttpServlet implements ResourceFactory
+{
+    private static final Logger LOG = Log.getLogger(DefaultServlet.class);
+
+    private static final long serialVersionUID = 4930458713846881193L;
+    private ServletContext _servletContext;
+    private ContextHandler _contextHandler;
+
+    private boolean _acceptRanges=true;
+    private boolean _dirAllowed=true;
+    private boolean _welcomeServlets=false;
+    private boolean _welcomeExactServlets=false;
+    private boolean _redirectWelcome=false;
+    private boolean _gzip=true;
+    private boolean _pathInfoOnly=false;
+    private boolean _etags=false;
+
+    private Resource _resourceBase;
+    private ResourceCache _cache;
+
+    private MimeTypes _mimeTypes;
+    private String[] _welcomes;
+    private Resource _stylesheet;
+    private boolean _useFileMappedBuffer=false;
+    private ByteArrayBuffer _cacheControl;
+    private String _relativeResourceBase;
+    private ServletHandler _servletHandler;
+    private ServletHolder _defaultHolder;
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init()
+    throws UnavailableException
+    {
+        _servletContext=getServletContext();
+        _contextHandler = initContextHandler(_servletContext);
+
+        _mimeTypes = _contextHandler.getMimeTypes();
+
+        _welcomes = _contextHandler.getWelcomeFiles();
+        if (_welcomes==null)
+            _welcomes=new String[] {"index.html","index.jsp"};
+
+        _acceptRanges=getInitBoolean("acceptRanges",_acceptRanges);
+        _dirAllowed=getInitBoolean("dirAllowed",_dirAllowed);
+        _redirectWelcome=getInitBoolean("redirectWelcome",_redirectWelcome);
+        _gzip=getInitBoolean("gzip",_gzip);
+        _pathInfoOnly=getInitBoolean("pathInfoOnly",_pathInfoOnly);
+
+        if ("exact".equals(getInitParameter("welcomeServlets")))
+        {
+            _welcomeExactServlets=true;
+            _welcomeServlets=false;
+        }
+        else
+            _welcomeServlets=getInitBoolean("welcomeServlets", _welcomeServlets);
+
+        if (getInitParameter("aliases")!=null)
+            _contextHandler.setAliases(getInitBoolean("aliases",false));
+
+        boolean aliases=_contextHandler.isAliases();
+        if (!aliases && !FileResource.getCheckAliases())
+            throw new IllegalStateException("Alias checking disabled");
+        if (aliases)
+            _servletContext.log("Aliases are enabled! Security constraints may be bypassed!!!");
+
+        _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer);
+
+        _relativeResourceBase = getInitParameter("relativeResourceBase");
+
+        String rb=getInitParameter("resourceBase");
+        if (rb!=null)
+        {
+            if (_relativeResourceBase!=null)
+                throw new  UnavailableException("resourceBase & relativeResourceBase");
+            try{_resourceBase=_contextHandler.newResource(rb);}
+            catch (Exception e)
+            {
+                LOG.warn(Log.EXCEPTION,e);
+                throw new UnavailableException(e.toString());
+            }
+        }
+
+        String css=getInitParameter("stylesheet");
+        try
+        {
+            if(css!=null)
+            {
+                _stylesheet = Resource.newResource(css);
+                if(!_stylesheet.exists())
+                {
+                    LOG.warn("!" + css);
+                    _stylesheet = null;
+                }
+            }
+            if(_stylesheet == null)
+            {
+                _stylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
+            }
+        }	
+        catch(Exception e)
+        {
+            LOG.warn(e.toString());
+            LOG.debug(e);
+        }
+
+        String t=getInitParameter("cacheControl");
+        if (t!=null)
+            _cacheControl=new ByteArrayBuffer(t);
+
+        String resourceCache = getInitParameter("resourceCache");
+        int max_cache_size=getInitInt("maxCacheSize", -2);
+        int max_cached_file_size=getInitInt("maxCachedFileSize", -2);
+        int max_cached_files=getInitInt("maxCachedFiles", -2);
+        if (resourceCache!=null)
+        {
+            if (max_cache_size!=-1 || max_cached_file_size!= -2 || max_cached_files!=-2)
+                LOG.debug("ignoring resource cache configuration, using resourceCache attribute");
+            if (_relativeResourceBase!=null || _resourceBase!=null)
+                throw new UnavailableException("resourceCache specified with resource bases");
+            _cache=(ResourceCache)_servletContext.getAttribute(resourceCache);
+
+            LOG.debug("Cache {}={}",resourceCache,_cache);
+        }
+
+        _etags = getInitBoolean("etags",_etags);
+        
+        try
+        {
+            if (_cache==null && max_cached_files>0)
+            {
+                _cache= new ResourceCache(null,this,_mimeTypes,_useFileMappedBuffer,_etags);
+
+                if (max_cache_size>0)
+                    _cache.setMaxCacheSize(max_cache_size);
+                if (max_cached_file_size>=-1)
+                    _cache.setMaxCachedFileSize(max_cached_file_size);
+                if (max_cached_files>=-1)
+                    _cache.setMaxCachedFiles(max_cached_files);
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            throw new UnavailableException(e.toString());
+        }
+
+        _servletHandler= (ServletHandler) _contextHandler.getChildHandlerByClass(ServletHandler.class);
+        for (ServletHolder h :_servletHandler.getServlets())
+            if (h.getServletInstance()==this)
+                _defaultHolder=h;
+
+        
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("resource base = "+_resourceBase);
+    }
+
+    /**
+     * Compute the field _contextHandler.<br/>
+     * In the case where the DefaultServlet is deployed on the HttpService it is likely that
+     * this method needs to be overwritten to unwrap the ServletContext facade until we reach
+     * the original jetty's ContextHandler.
+     * @param servletContext The servletContext of this servlet.
+     * @return the jetty's ContextHandler for this servletContext.
+     */
+    protected ContextHandler initContextHandler(ServletContext servletContext)
+    {
+        ContextHandler.Context scontext=ContextHandler.getCurrentContext();
+        if (scontext==null)
+        {
+            if (servletContext instanceof ContextHandler.Context)
+                return ((ContextHandler.Context)servletContext).getContextHandler();
+            else
+                throw new IllegalArgumentException("The servletContext " + servletContext + " " + 
+                    servletContext.getClass().getName() + " is not " + ContextHandler.Context.class.getName());
+        }
+        else
+            return ContextHandler.getCurrentContext().getContextHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getInitParameter(String name)
+    {
+        String value=getServletContext().getInitParameter("org.eclipse.jetty.servlet.Default."+name);
+        if (value==null)
+            value=super.getInitParameter(name);
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean getInitBoolean(String name, boolean dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null || value.length()==0)
+            return dft;
+        return (value.startsWith("t")||
+                value.startsWith("T")||
+                value.startsWith("y")||
+                value.startsWith("Y")||
+                value.startsWith("1"));
+    }
+
+    /* ------------------------------------------------------------ */
+    private int getInitInt(String name, int dft)
+    {
+        String value=getInitParameter(name);
+        if (value==null)
+            value=getInitParameter(name);
+        if (value!=null && value.length()>0)
+            return Integer.parseInt(value);
+        return dft;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** get Resource to serve.
+     * Map a path to a resource. The default implementation calls
+     * HttpContext.getResource but derived servlets may provide
+     * their own mapping.
+     * @param pathInContext The path to find a resource for.
+     * @return The resource to serve.
+     */
+    public Resource getResource(String pathInContext)
+    {	
+        Resource r=null;
+        if (_relativeResourceBase!=null)
+            pathInContext=URIUtil.addPaths(_relativeResourceBase,pathInContext);
+
+        try
+        {
+            if (_resourceBase!=null)
+            {
+                r = _resourceBase.addPath(pathInContext);
+                if (!_contextHandler.checkAlias(pathInContext,r))
+                    r=null;
+            }
+            else if (_servletContext instanceof ContextHandler.Context)
+            {
+                r = _contextHandler.getResource(pathInContext);
+            }
+            else
+            {
+                URL u = _servletContext.getResource(pathInContext);
+                r = _contextHandler.newResource(u);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Resource "+pathInContext+"="+r);
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+
+        if((r==null || !r.exists()) && pathInContext.endsWith("/jetty-dir.css"))
+            r=_stylesheet;
+
+        return r;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        String servletPath=null;
+        String pathInfo=null;
+        Enumeration<String> reqRanges = null;
+        Boolean included =request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null;
+        if (included!=null && included.booleanValue())
+        {
+            servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            included = Boolean.FALSE;
+            servletPath = _pathInfoOnly?"/":request.getServletPath();
+            pathInfo = request.getPathInfo();
+
+            // Is this a Range request?
+            reqRanges = request.getHeaders(HttpHeaders.RANGE);
+            if (!hasDefinedRange(reqRanges))
+                reqRanges = null;
+        }
+        
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        boolean endsWithSlash=(pathInfo==null?request.getServletPath():pathInfo).endsWith(URIUtil.SLASH);
+        
+
+        // Find the resource and content
+        Resource resource=null;
+        HttpContent content=null;
+        try
+        {
+            // is gzip enabled?
+            String pathInContextGz=null;
+            boolean gzip=false;
+            if (!included.booleanValue() && _gzip && reqRanges==null && !endsWithSlash )
+            {
+                // Look for a gzip resource
+                pathInContextGz=pathInContext+".gz";
+                if (_cache==null)
+                    resource=getResource(pathInContextGz);
+                else
+                {
+                    content=_cache.lookup(pathInContextGz);
+                    resource=(content==null)?null:content.getResource();
+                }
+
+                // Does a gzip resource exist?
+                if (resource!=null && resource.exists() && !resource.isDirectory())
+                {
+                    // Tell caches that response may vary by accept-encoding
+                    response.addHeader(HttpHeaders.VARY,HttpHeaders.ACCEPT_ENCODING);
+                    
+                    // Does the client accept gzip?
+                    String accept=request.getHeader(HttpHeaders.ACCEPT_ENCODING);
+                    if (accept!=null && accept.indexOf("gzip")>=0)
+                        gzip=true;
+                }
+            }
+
+            // find resource
+            if (!gzip)
+            {
+                if (_cache==null)
+                    resource=getResource(pathInContext);
+                else
+                {
+                    content=_cache.lookup(pathInContext);
+                    resource=content==null?null:content.getResource();
+                }
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("uri="+request.getRequestURI()+" resource="+resource+(content!=null?" content":""));
+            
+            // Handle resource
+            if (resource==null || !resource.exists())
+            {
+                if (included) 
+                    throw new FileNotFoundException("!" + pathInContext);
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            }
+            else if (!resource.isDirectory())
+            {
+                if (endsWithSlash && _contextHandler.isAliases() && pathInContext.length()>1)
+                {
+                    String q=request.getQueryString();
+                    pathInContext=pathInContext.substring(0,pathInContext.length()-1);
+                    if (q!=null&&q.length()!=0)
+                        pathInContext+="?"+q;
+                    response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(_servletContext.getContextPath(),pathInContext)));
+                }
+                else
+                {
+                    // ensure we have content
+                    if (content==null)
+                        content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),response.getBufferSize(),_etags);
+
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                    {
+                        if (gzip)
+                        {
+                            response.setHeader(HttpHeaders.CONTENT_ENCODING,"gzip");
+                            String mt=_servletContext.getMimeType(pathInContext);
+                            if (mt!=null)
+                                response.setContentType(mt);
+                        }
+                        sendData(request,response,included.booleanValue(),resource,content,reqRanges);
+                    }
+                }
+            }
+            else
+            {
+                String welcome=null;
+
+                if (!endsWithSlash || (pathInContext.length()==1 && request.getAttribute("org.eclipse.jetty.server.nullPathInfo")!=null))
+                {
+                    StringBuffer buf=request.getRequestURL();
+                    synchronized(buf)
+                    {
+                        int param=buf.lastIndexOf(";");
+                        if (param<0)
+                            buf.append('/');
+                        else
+                            buf.insert(param,'/');
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                        {
+                            buf.append('?');
+                            buf.append(q);
+                        }
+                        response.setContentLength(0);
+                        response.sendRedirect(response.encodeRedirectURL(buf.toString()));
+                    }
+                }
+                // else look for a welcome file
+                else if (null!=(welcome=getWelcomeFile(pathInContext)))
+                {
+                    LOG.debug("welcome={}",welcome);
+                    if (_redirectWelcome)
+                    {
+                        // Redirect to the index
+                        response.setContentLength(0);
+                        String q=request.getQueryString();
+                        if (q!=null&&q.length()!=0)
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)+"?"+q));
+                        else
+                            response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths( _servletContext.getContextPath(),welcome)));
+                    }
+                    else
+                    {
+                        // Forward to the index
+                        RequestDispatcher dispatcher=request.getRequestDispatcher(welcome);
+                        if (dispatcher!=null)
+                        {
+                            if (included.booleanValue())
+                                dispatcher.include(request,response);
+                            else
+                            {
+                                request.setAttribute("org.eclipse.jetty.server.welcome",welcome);
+                                dispatcher.forward(request,response);
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    content=new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),_etags);
+                    if (included.booleanValue() || passConditionalHeaders(request,response, resource,content))
+                        sendDirectory(request,response,resource,pathInContext);
+                }
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            LOG.warn(Log.EXCEPTION,e);
+            if(!response.isCommitted())
+                response.sendError(500, e.getMessage());
+        }
+        finally
+        {
+            if (content!=null)
+                content.release();
+            else if (resource!=null)
+                resource.release();
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean hasDefinedRange(Enumeration<String> reqRanges)
+    {
+        return (reqRanges!=null && reqRanges.hasMoreElements());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        doGet(request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
+    throws ServletException, IOException
+    {
+        resp.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finds a matching welcome file for the supplied {@link Resource}. This will be the first entry in the list of
+     * configured {@link #_welcomes welcome files} that existing within the directory referenced by the <code>Resource</code>.
+     * If the resource is not a directory, or no matching file is found, then it may look for a valid servlet mapping.
+     * If there is none, then <code>null</code> is returned.
+     * The list of welcome files is read from the {@link ContextHandler} for this servlet, or
+     * <code>"index.jsp" , "index.html"</code> if that is <code>null</code>.
+     * @param resource
+     * @return The path of the matching welcome file in context or null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    private String getWelcomeFile(String pathInContext) throws MalformedURLException, IOException
+    {
+        if (_welcomes==null)
+            return null;
+
+        String welcome_servlet=null;
+        for (int i=0;i<_welcomes.length;i++)
+        {
+            String welcome_in_context=URIUtil.addPaths(pathInContext,_welcomes[i]);
+            Resource welcome=getResource(welcome_in_context);
+            if (welcome!=null && welcome.exists())
+                return _welcomes[i];
+
+            if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
+            {
+                Map.Entry entry=_servletHandler.getHolderEntry(welcome_in_context);
+                if (entry!=null && entry.getValue()!=_defaultHolder &&
+                        (_welcomeServlets || (_welcomeExactServlets && entry.getKey().equals(welcome_in_context))))
+                    welcome_servlet=welcome_in_context;
+
+            }
+        }
+        return welcome_servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Check modification date headers.
+     */
+    protected boolean passConditionalHeaders(HttpServletRequest request,HttpServletResponse response, Resource resource, HttpContent content)
+    throws IOException
+    {
+        try
+        {
+            if (!request.getMethod().equals(HttpMethods.HEAD) )
+            {
+                if (_etags)
+                {
+                    String ifm=request.getHeader(HttpHeaders.IF_MATCH);
+                    if (ifm!=null)
+                    {
+                        boolean match=false;
+                        if (content!=null && content.getETag()!=null)
+                        {
+                            QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifm,", ",false,true);
+                            while (!match && quoted.hasMoreTokens())
+                            {
+                                String tag = quoted.nextToken();
+                                if (content.getETag().toString().equals(tag))
+                                    match=true;
+                            }
+                        }
+
+                        if (!match)
+                        {
+                            Response r = Response.getResponse(response);
+                            r.reset(true);
+                            r.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+                            return false;
+                        }
+                    }
+                    
+                    String ifnm=request.getHeader(HttpHeaders.IF_NONE_MATCH);
+                    if (ifnm!=null && content!=null && content.getETag()!=null)
+                    {
+                        // Look for GzipFiltered version of etag
+                        if (content.getETag().toString().equals(request.getAttribute("o.e.j.s.GzipFilter.ETag")))
+                        {
+                            Response r = Response.getResponse(response);
+                            r.reset(true);
+                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,ifnm);
+                            return false;
+                        }
+                        
+                        
+                        // Handle special case of exact match.
+                        if (content.getETag().toString().equals(ifnm))
+                        {
+                            Response r = Response.getResponse(response);
+                            r.reset(true);
+                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
+                            return false;
+                        }
+
+                        // Handle list of tags
+                        QuotedStringTokenizer quoted = new QuotedStringTokenizer(ifnm,", ",false,true);
+                        while (quoted.hasMoreTokens())
+                        {
+                            String tag = quoted.nextToken();
+                            if (content.getETag().toString().equals(tag))
+                            {
+                                Response r = Response.getResponse(response);
+                                r.reset(true);
+                                r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                r.getHttpFields().put(HttpHeaders.ETAG_BUFFER,content.getETag());
+                                return false;
+                            }
+                        }
+                        
+                        return true;
+                    }
+                }
+                
+                String ifms=request.getHeader(HttpHeaders.IF_MODIFIED_SINCE);
+                if (ifms!=null)
+                {
+                    //Get jetty's Response impl
+                    Response r = Response.getResponse(response);
+                                       
+                    if (content!=null)
+                    {
+                        Buffer mdlm=content.getLastModified();
+                        if (mdlm!=null)
+                        {
+                            if (ifms.equals(mdlm.toString()))
+                            {
+                                r.reset(true);
+                                r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                                if (_etags)
+                                    r.getHttpFields().add(HttpHeaders.ETAG_BUFFER,content.getETag());
+                                r.flushBuffer();
+                                return false;
+                            }
+                        }
+                    }
+
+                    long ifmsl=request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE);
+                    if (ifmsl!=-1)
+                    {
+                        if (resource.lastModified()/1000 <= ifmsl/1000)
+                        { 
+                            r.reset(true);
+                            r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                            if (_etags)
+                                r.getHttpFields().add(HttpHeaders.ETAG_BUFFER,content.getETag());
+                            r.flushBuffer();
+                            return false;
+                        }
+                    }
+                }
+
+                // Parse the if[un]modified dates and compare to resource
+                long date=request.getDateHeader(HttpHeaders.IF_UNMODIFIED_SINCE);
+
+                if (date!=-1)
+                {
+                    if (resource.lastModified()/1000 > date/1000)
+                    {
+                        response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                        return false;
+                    }
+                }
+
+            }
+        }
+        catch(IllegalArgumentException iae)
+        {
+            if(!response.isCommitted())
+                response.sendError(400, iae.getMessage());
+            throw iae;
+        }
+        return true;
+    }
+
+
+    /* ------------------------------------------------------------------- */
+    protected void sendDirectory(HttpServletRequest request,
+            HttpServletResponse response,
+            Resource resource,
+            String pathInContext)
+    throws IOException
+    {
+        if (!_dirAllowed)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+
+        byte[] data=null;
+        String base = URIUtil.addPaths(request.getRequestURI(),URIUtil.SLASH);
+
+        //If the DefaultServlet has a resource base set, use it
+        if (_resourceBase != null)
+        {
+            // handle ResourceCollection
+            if (_resourceBase instanceof ResourceCollection)
+                resource=_resourceBase.addPath(pathInContext);
+        }
+        //Otherwise, try using the resource base of its enclosing context handler
+        else if (_contextHandler.getBaseResource() instanceof ResourceCollection)
+            resource=_contextHandler.getBaseResource().addPath(pathInContext);
+
+        String dir = resource.getListHTML(base,pathInContext.length()>1);
+        if (dir==null)
+        {
+            response.sendError(HttpServletResponse.SC_FORBIDDEN,
+            "No directory");
+            return;
+        }
+
+        data=dir.getBytes("UTF-8");
+        response.setContentType("text/html; charset=UTF-8");
+        response.setContentLength(data.length);
+        response.getOutputStream().write(data);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void sendData(HttpServletRequest request,
+            HttpServletResponse response,
+            boolean include,
+            Resource resource,
+            HttpContent content,
+            Enumeration reqRanges)
+    throws IOException
+    {
+        boolean direct;
+        long content_length;
+        if (content==null)
+        {
+            direct=false;
+            content_length=resource.length();
+        }
+        else
+        {
+            Connector connector = AbstractHttpConnection.getCurrentConnection().getConnector();
+            direct=connector instanceof NIOConnector && ((NIOConnector)connector).getUseDirectBuffers() && !(connector instanceof SslConnector);
+            content_length=content.getContentLength();
+        }
+
+
+        // Get the output stream (or writer)
+        OutputStream out =null;
+        boolean written;
+        try
+        {
+            out = response.getOutputStream();
+
+            // has a filter already written to the response?
+            written = out instanceof HttpOutput 
+                ? ((HttpOutput)out).isWritten() 
+                : AbstractHttpConnection.getCurrentConnection().getGenerator().isWritten();
+        }
+        catch(IllegalStateException e) 
+        {
+            out = new WriterOutputStream(response.getWriter());
+            written=true; // there may be data in writer buffer, so assume written
+        }
+        
+        if ( reqRanges == null || !reqRanges.hasMoreElements() || content_length<0)
+        {
+            //  if there were no ranges, send entire entity
+            if (include)
+            {
+                resource.writeTo(out,0,content_length);
+            }
+            else
+            {
+                // See if a direct methods can be used?
+                if (content!=null && !written && out instanceof HttpOutput)
+                {
+                    if (response instanceof Response)
+                    {
+                        writeOptionHeaders(((Response)response).getHttpFields());
+                        ((AbstractHttpConnection.Output)out).sendContent(content);
+                    }
+                    else 
+                    {
+                        Buffer buffer = direct?content.getDirectBuffer():content.getIndirectBuffer();
+                        if (buffer!=null)
+                        {
+                            writeHeaders(response,content,content_length);
+                            ((AbstractHttpConnection.Output)out).sendContent(buffer);
+                        }
+                        else
+                        {
+                            writeHeaders(response,content,content_length);
+                            resource.writeTo(out,0,content_length);
+                        }
+                    }
+                }
+                else 
+                {
+                    // Write headers normally
+                    writeHeaders(response,content,written?-1:content_length);
+
+                    // Write content normally
+                    Buffer buffer = (content==null)?null:content.getIndirectBuffer();
+                    if (buffer!=null)
+                        buffer.writeTo(out);
+                    else
+                        resource.writeTo(out,0,content_length);
+                }
+            }
+        }
+        else
+        {
+            // Parse the satisfiable ranges
+            List ranges =InclusiveByteRange.satisfiableRanges(reqRanges,content_length);
+
+            //  if there are no satisfiable ranges, send 416 response
+            if (ranges==null || ranges.size()==0)
+            {
+                writeHeaders(response, content, content_length);
+                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+                response.setHeader(HttpHeaders.CONTENT_RANGE,
+                        InclusiveByteRange.to416HeaderRangeString(content_length));
+                resource.writeTo(out,0,content_length);
+                return;
+            }
+
+            //  if there is only a single valid range (must be satisfiable
+            //  since were here now), send that range with a 216 response
+            if ( ranges.size()== 1)
+            {
+                InclusiveByteRange singleSatisfiableRange =
+                    (InclusiveByteRange)ranges.get(0);
+                long singleLength = singleSatisfiableRange.getSize(content_length);
+                writeHeaders(response,content,singleLength                     );
+                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+                response.setHeader(HttpHeaders.CONTENT_RANGE,
+                        singleSatisfiableRange.toHeaderRangeString(content_length));
+                resource.writeTo(out,singleSatisfiableRange.getFirst(content_length),singleLength);
+                return;
+            }
+
+            //  multiple non-overlapping valid ranges cause a multipart
+            //  216 response which does not require an overall
+            //  content-length header
+            //
+            writeHeaders(response,content,-1);
+            String mimetype=(content.getContentType()==null?null:content.getContentType().toString());
+            if (mimetype==null)
+                LOG.warn("Unknown mimetype for "+request.getRequestURI());
+            MultiPartOutputStream multi = new MultiPartOutputStream(out);
+            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+
+            // If the request has a "Request-Range" header then we need to
+            // send an old style multipart/x-byteranges Content-Type. This
+            // keeps Netscape and acrobat happy. This is what Apache does.
+            String ctp;
+            if (request.getHeader(HttpHeaders.REQUEST_RANGE)!=null)
+                ctp = "multipart/x-byteranges; boundary=";
+            else
+                ctp = "multipart/byteranges; boundary=";
+            response.setContentType(ctp+multi.getBoundary());
+
+            InputStream in=resource.getInputStream();
+            long pos=0;
+
+            // calculate the content-length
+            int length=0;
+            String[] header = new String[ranges.size()];
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+                header[i]=ibr.toHeaderRangeString(content_length);
+                length+=
+                    ((i>0)?2:0)+
+                    2+multi.getBoundary().length()+2+
+                    (mimetype==null?0:HttpHeaders.CONTENT_TYPE.length()+2+mimetype.length())+2+
+                    HttpHeaders.CONTENT_RANGE.length()+2+header[i].length()+2+
+                    2+
+                    (ibr.getLast(content_length)-ibr.getFirst(content_length))+1;
+            }
+            length+=2+2+multi.getBoundary().length()+2+2;
+            response.setContentLength(length);
+
+            for (int i=0;i<ranges.size();i++)
+            {
+                InclusiveByteRange ibr = (InclusiveByteRange) ranges.get(i);
+                multi.startPart(mimetype,new String[]{HttpHeaders.CONTENT_RANGE+": "+header[i]});
+
+                long start=ibr.getFirst(content_length);
+                long size=ibr.getSize(content_length);
+                if (in!=null)
+                {
+                    // Handle non cached resource
+                    if (start<pos)
+                    {
+                        in.close();
+                        in=resource.getInputStream();
+                        pos=0;
+                    }
+                    if (pos<start)
+                    {
+                        in.skip(start-pos);
+                        pos=start;
+                    }
+                    IO.copy(in,multi,size);
+                    pos+=size;
+                }
+                else
+                    // Handle cached resource
+                    (resource).writeTo(multi,start,size);
+
+            }
+            if (in!=null)
+                in.close();
+            multi.close();
+        }
+        return;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeHeaders(HttpServletResponse response,HttpContent content,long count)
+    throws IOException
+    {        
+        if (content.getContentType()!=null && response.getContentType()==null)
+            response.setContentType(content.getContentType().toString());
+
+        if (response instanceof Response)
+        {
+            Response r=(Response)response;
+            HttpFields fields = r.getHttpFields();
+
+            if (content.getLastModified()!=null)
+                fields.put(HttpHeaders.LAST_MODIFIED_BUFFER,content.getLastModified());
+            else if (content.getResource()!=null)
+            {
+                long lml=content.getResource().lastModified();
+                if (lml!=-1)
+                    fields.putDateField(HttpHeaders.LAST_MODIFIED_BUFFER,lml);
+            }
+
+            if (count != -1)
+                r.setLongContentLength(count);
+
+            writeOptionHeaders(fields);
+            
+            if (_etags)
+                fields.put(HttpHeaders.ETAG_BUFFER,content.getETag());
+        }
+        else
+        {
+            long lml=content.getResource().lastModified();
+            if (lml>=0)
+                response.setDateHeader(HttpHeaders.LAST_MODIFIED,lml);
+
+            if (count != -1)
+            {
+                if (count<Integer.MAX_VALUE)
+                    response.setContentLength((int)count);
+                else
+                    response.setHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(count));
+            }
+
+            writeOptionHeaders(response);
+
+            if (_etags)
+                response.setHeader(HttpHeaders.ETAG,content.getETag().toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpFields fields) throws IOException
+    {
+        if (_acceptRanges)
+            fields.put(HttpHeaders.ACCEPT_RANGES_BUFFER,HttpHeaderValues.BYTES_BUFFER);
+
+        if (_cacheControl!=null)
+            fields.put(HttpHeaders.CACHE_CONTROL_BUFFER,_cacheControl);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void writeOptionHeaders(HttpServletResponse response) throws IOException
+    {
+        if (_acceptRanges)
+            response.setHeader(HttpHeaders.ACCEPT_RANGES,"bytes");
+
+        if (_cacheControl!=null)
+            response.setHeader(HttpHeaders.CACHE_CONTROL,_cacheControl.toString());
+    }
+    
+  
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see javax.servlet.Servlet#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+        if (_cache!=null)
+            _cache.flushCache();
+        super.destroy();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java b/src/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
new file mode 100644
index 0000000..a89073c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/ErrorPageErrorHandler.java
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+
+/* ------------------------------------------------------------ */
+/** Error Page Error Handler
+ *
+ * An ErrorHandler that maps exceptions and status codes to URIs for dispatch using
+ * the internal ERROR style of dispatch.
+ *
+ */
+public class ErrorPageErrorHandler extends ErrorHandler implements ErrorHandler.ErrorPageMapper
+{
+    public final static String GLOBAL_ERROR_PAGE = "org.eclipse.jetty.server.error_page.global";
+
+    protected ServletContext _servletContext;
+    private final Map<String,String> _errorPages= new HashMap<String,String>(); // code or exception to URL
+    private final List<ErrorCodeRange> _errorPageList=new ArrayList<ErrorCodeRange>(); // list of ErrorCode by range
+
+    /* ------------------------------------------------------------ */
+    public ErrorPageErrorHandler()
+    {}
+
+    @Override
+    public String getErrorPage(HttpServletRequest request)
+    {
+        String error_page= null;
+        Class<?> exClass= (Class<?>)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
+
+        if (ServletException.class.equals(exClass))
+        {
+            error_page= (String)_errorPages.get(exClass.getName());
+            if (error_page == null)
+            {
+                Throwable th= (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
+                while (th instanceof ServletException)
+                    th= ((ServletException)th).getRootCause();
+                if (th != null)
+                    exClass= th.getClass();
+            }
+        }
+
+        while (error_page == null && exClass != null )
+        {
+            error_page= (String)_errorPages.get(exClass.getName());
+            exClass= exClass.getSuperclass();
+        }
+
+        if (error_page == null)
+        {
+            // look for an exact code match
+            Integer code=(Integer)request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
+            if (code!=null)
+            {
+                error_page= (String)_errorPages.get(Integer.toString(code));
+
+                // if still not found
+                if ((error_page == null) && (_errorPageList != null))
+                {
+                    // look for an error code range match.
+                    for (int i = 0; i < _errorPageList.size(); i++)
+                    {
+                        ErrorCodeRange errCode = (ErrorCodeRange) _errorPageList.get(i);
+                        if (errCode.isInRange(code))
+                        {
+                            error_page = errCode.getUri();
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        //try new servlet 3.0 global error page
+        if (error_page == null)
+        {
+            error_page = _errorPages.get(GLOBAL_ERROR_PAGE);
+        }
+
+        return error_page;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the errorPages.
+     */
+    public Map<String,String> getErrorPages()
+    {
+        return _errorPages;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param errorPages The errorPages to set. A map of Exception class name  or error code as a string to URI string
+     */
+    public void setErrorPages(Map<String,String> errorPages)
+    {
+        _errorPages.clear();
+        if (errorPages!=null)
+            _errorPages.putAll(errorPages);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exception The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(Class<? extends Throwable> exception,String uri)
+    {
+        _errorPages.put(exception.getName(),uri);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for an exception class
+     * This method is called as a result of an exception-type element in a web.xml file
+     * or may be called directly
+     * @param exceptionClassName The exception
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(String exceptionClassName,String uri)
+    {
+        _errorPages.put(exceptionClassName,uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code.
+     * This method is called as a result of an error-code element in a web.xml file
+     * or may be called directly
+     * @param code The HTTP status code to match
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int code,String uri)
+    {
+        _errorPages.put(Integer.toString(code),uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add Error Page mapping for a status code range.
+     * This method is not available from web.xml and must be called
+     * directly.
+     * @param from The lowest matching status code
+     * @param to The highest matching status code
+     * @param uri The URI of the error page.
+     */
+    public void addErrorPage(int from, int to, String uri)
+    {
+        _errorPageList.add(new ErrorCodeRange(from, to, uri));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _servletContext=ContextHandler.getCurrentContext();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class ErrorCodeRange
+    {
+        private int _from;
+        private int _to;
+        private String _uri;
+
+        ErrorCodeRange(int from, int to, String uri)
+            throws IllegalArgumentException
+        {
+            if (from > to)
+                throw new IllegalArgumentException("from>to");
+
+            _from = from;
+            _to = to;
+            _uri = uri;
+        }
+
+        boolean isInRange(int value)
+        {
+            if ((value >= _from) && (value <= _to))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        String getUri()
+        {
+            return _uri;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "from: " + _from + ",to: " + _to + ",uri: " + _uri;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/FilterHolder.java b/src/java/org/eclipse/jetty/servlet/FilterHolder.java
new file mode 100644
index 0000000..3b85f60
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/FilterHolder.java
@@ -0,0 +1,259 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterConfig;
+import javax.servlet.FilterRegistration;
+import javax.servlet.ServletException;
+
+import org.eclipse.jetty.servlet.Holder.Source;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/** 
+ * 
+ */
+public class FilterHolder extends Holder<Filter>
+{
+    private static final Logger LOG = Log.getLogger(FilterHolder.class);
+    
+    /* ------------------------------------------------------------ */
+    private transient Filter _filter;
+    private transient Config _config;
+    private transient FilterRegistration.Dynamic _registration;
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor 
+     */
+    public FilterHolder()
+    {
+        this(Source.EMBEDDED);
+    }   
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor 
+     */
+    public FilterHolder(Holder.Source source)
+    {
+        super(source);
+    }   
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor 
+     */
+    public FilterHolder(Class<? extends Filter> filter)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(filter);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing filter.
+     */
+    public FilterHolder(Filter filter)
+    {
+        this(Source.EMBEDDED);
+        setFilter(filter);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStart()
+        throws Exception
+    {
+        super.doStart();
+        
+        if (!javax.servlet.Filter.class
+            .isAssignableFrom(_class))
+        {
+            String msg = _class+" is not a javax.servlet.Filter";
+            super.stop();
+            throw new IllegalStateException(msg);
+        }
+
+        if (_filter==null)
+        {
+            try
+            {
+                _filter=((ServletContextHandler.Context)_servletHandler.getServletContext()).createFilter(getHeldClass());
+            }
+            catch (ServletException se)
+            {
+                Throwable cause = se.getRootCause();
+                if (cause instanceof InstantiationException)
+                    throw (InstantiationException)cause;
+                if (cause instanceof IllegalAccessException)
+                    throw (IllegalAccessException)cause;
+                throw se;
+            }
+        }
+        
+        _config=new Config();
+        _filter.init(_config);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {      
+        if (_filter!=null)
+        {
+            try
+            {
+                destroyInstance(_filter);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+        }
+        if (!_extInstance)
+            _filter=null;
+        
+        _config=null;
+        super.doStop();   
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroyInstance (Object o)
+        throws Exception
+    {
+        if (o==null)
+            return;
+        Filter f = (Filter)o;
+        f.destroy();
+        getServletHandler().destroyFilter(f);
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilter(Filter filter)
+    {
+        _filter=filter;
+        _extInstance=true;
+        setHeldClass(filter.getClass());
+        if (getName()==null)
+            setName(filter.getClass().getName());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Filter getFilter()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return getName();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public FilterRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Registration extends HolderRegistration implements FilterRegistration.Dynamic
+    {
+        public void addMappingForServletNames(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... servletNames)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setServletNames(servletNames);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public void addMappingForUrlPatterns(EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterHolder(FilterHolder.this);
+            mapping.setPathSpecs(urlPatterns);
+            mapping.setDispatcherTypes(dispatcherTypes);
+            if (isMatchAfter)
+                _servletHandler.addFilterMapping(mapping);
+            else
+                _servletHandler.prependFilterMapping(mapping);
+        }
+
+        public Collection<String> getServletNameMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> names=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] servlets=mapping.getServletNames();
+                if (servlets!=null && servlets.length>0)
+                    names.addAll(Arrays.asList(servlets));
+            }
+            return names;
+        }
+
+        public Collection<String> getUrlPatternMappings()
+        {
+            FilterMapping[] mappings =_servletHandler.getFilterMappings();
+            List<String> patterns=new ArrayList<String>();
+            for (FilterMapping mapping : mappings)
+            {
+                if (mapping.getFilterHolder()!=FilterHolder.this)
+                    continue;
+                String[] specs=mapping.getPathSpecs();
+                patterns.addAll(TypeUtil.asList(specs));
+            }
+            return patterns;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    class Config extends HolderConfig implements FilterConfig
+    {
+        /* ------------------------------------------------------------ */
+        public String getFilterName()
+        {
+            return _name;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/FilterMapping.java b/src/java/org/eclipse/jetty/servlet/FilterMapping.java
new file mode 100644
index 0000000..2047451
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/FilterMapping.java
@@ -0,0 +1,276 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+
+
+public class FilterMapping implements Dumpable
+{
+    /** Dispatch types */
+    public static final int DEFAULT=0;
+    public static final int REQUEST=1;
+    public static final int FORWARD=2;
+    public static final int INCLUDE=4;
+    public static final int ERROR=8;
+    public static final int ASYNC=16;
+    public static final int ALL=31;
+    
+
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static DispatcherType dispatch(String type)
+    {
+        if ("request".equalsIgnoreCase(type))
+            return DispatcherType.REQUEST;
+        if ("forward".equalsIgnoreCase(type))
+            return DispatcherType.FORWARD;
+        if ("include".equalsIgnoreCase(type))
+            return DispatcherType.INCLUDE;
+        if ("error".equalsIgnoreCase(type))
+            return DispatcherType.ERROR;
+        if ("async".equalsIgnoreCase(type))
+            return DispatcherType.ASYNC;
+        throw new IllegalArgumentException(type);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Dispatch type from name
+     */
+    public static int dispatch(DispatcherType type)
+    {
+    	switch(type)
+    	{
+    	  case REQUEST:
+    		  return REQUEST;
+    	  case ASYNC:
+    		  return ASYNC;
+    	  case FORWARD:
+    		  return FORWARD;
+    	  case INCLUDE:
+    		  return INCLUDE;
+    	  case ERROR:
+    		  return ERROR;
+    	}
+        throw new IllegalArgumentException(type.toString());
+    }
+	
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    
+	
+    private int _dispatches=DEFAULT;
+    private String _filterName;
+    private transient FilterHolder _holder;
+    private String[] _pathSpecs;
+    private String[] _servletNames;
+
+    /* ------------------------------------------------------------ */
+    public FilterMapping()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a path.
+     * @param path The path to check or null to just check type
+     * @param type The type of request: __REQUEST,__FORWARD,__INCLUDE, __ASYNC or __ERROR.
+     * @return True if this filter applies
+     */
+    boolean appliesTo(String path, int type)
+    {
+        if (appliesTo(type))
+        {
+            for (int i=0;i<_pathSpecs.length;i++)
+                if (_pathSpecs[i]!=null &&  PathMap.match(_pathSpecs[i], path,true))
+                    return true;
+        }
+
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Check if this filter applies to a particular dispatch type.
+     * @param type The type of request:
+     *      {@link Handler#REQUEST}, {@link Handler#FORWARD}, {@link Handler#INCLUDE} or {@link Handler#ERROR}.
+     * @return <code>true</code> if this filter applies
+     */
+    boolean appliesTo(int type)
+    {
+    	if (_dispatches==0)
+    		return type==REQUEST || type==ASYNC && _holder.isAsyncSupported();
+        return (_dispatches&type)!=0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterName.
+     */
+    public String getFilterName()
+    {
+        return _filterName;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the holder.
+     */
+    FilterHolder getFilterHolder()
+    {
+        return _holder;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpec.
+     */
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDispatcherTypes(EnumSet<DispatcherType> dispatcherTypes) 
+    {
+        _dispatches=DEFAULT;
+        if (dispatcherTypes!=null)
+        {
+            if (dispatcherTypes.contains(DispatcherType.ERROR)) 
+                _dispatches|=ERROR;
+            if (dispatcherTypes.contains(DispatcherType.FORWARD)) 
+                _dispatches|=FORWARD;
+            if (dispatcherTypes.contains(DispatcherType.INCLUDE)) 
+                _dispatches|=INCLUDE;
+            if (dispatcherTypes.contains(DispatcherType.REQUEST)) 
+                _dispatches|=REQUEST;
+            if (dispatcherTypes.contains(DispatcherType.ASYNC)) 
+                _dispatches|=ASYNC;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param dispatches The dispatches to set.
+     * @see #DEFAULT
+     * @see #REQUEST
+     * @see #ERROR
+     * @see #FORWARD
+     * @see #INCLUDE
+     */
+    public void setDispatches(int dispatches)
+    {
+        _dispatches = dispatches;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterName The filterName to set.
+     */
+    public void setFilterName(String filterName)
+    {
+        _filterName = filterName;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param holder The holder to set.
+     */
+    void setFilterHolder(FilterHolder holder)
+    {
+        _holder = holder;
+        setFilterName(holder.getName());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The Path specifications to which this filter should be mapped. 
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    public String[] getServletNames()
+    {
+        return _servletNames;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletNames Maps the {@link #setFilterName(String) named filter} to multiple servlets
+     * @see #setServletName
+     */
+    public void setServletNames(String[] servletNames)
+    {
+        _servletNames = servletNames;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName Maps the {@link #setFilterName(String) named filter} to a single servlet
+     * @see #setServletNames
+     */
+    public void setServletName(String servletName)
+    {
+        _servletNames = new String[]{servletName};
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return 
+        TypeUtil.asList(_pathSpecs)+"/"+
+        TypeUtil.asList(_servletNames)+"=="+
+        _dispatches+"=>"+
+        _filterName; 
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }    
+}
diff --git a/src/java/org/eclipse/jetty/servlet/Holder.java b/src/java/org/eclipse/jetty/servlet/Holder.java
new file mode 100644
index 0000000..f46211a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/Holder.java
@@ -0,0 +1,408 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Registration;
+import javax.servlet.ServletContext;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* --------------------------------------------------------------------- */
+/** 
+ * 
+ */
+public class Holder<T> extends AbstractLifeCycle implements Dumpable
+{
+    public enum Source { EMBEDDED, JAVAX_API, DESCRIPTOR, ANNOTATION };
+    final private Source _source;
+    private static final Logger LOG = Log.getLogger(Holder.class);
+
+    protected transient Class<? extends T> _class;
+    protected final Map<String,String> _initParams=new HashMap<String,String>(3);
+    protected String _className;
+    protected String _displayName;
+    protected boolean _extInstance;
+    protected boolean _asyncSupported;
+
+    /* ---------------------------------------------------------------- */
+    protected String _name;
+    protected ServletHandler _servletHandler;
+
+    /* ---------------------------------------------------------------- */
+    protected Holder(Source source)
+    {
+        _source=source;
+        switch(_source)
+        {
+            case JAVAX_API:
+            case DESCRIPTOR:
+            case ANNOTATION:
+                _asyncSupported=false;
+                break;
+            default:
+                _asyncSupported=true;
+        }
+    }
+    
+    public Source getSource()
+    {
+        return _source;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this holder was created for a specific instance.
+     */
+    public boolean isInstance()
+    {
+        return _extInstance;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public void doStart()
+        throws Exception
+    {
+        //if no class already loaded and no classname, make servlet permanently unavailable
+        if (_class==null && (_className==null || _className.equals("")))
+            throw new UnavailableException("No class for Servlet or Filter for "+_name);
+        
+        //try to load class
+        if (_class==null)
+        {
+            try
+            {
+                _class=Loader.loadClass(Holder.class, _className);
+                if(LOG.isDebugEnabled())
+                    LOG.debug("Holding {}",_class);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                throw new UnavailableException(e.getMessage());
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doStop()
+        throws Exception
+    {
+        if (!_extInstance)
+            _class=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getClassName()
+    {
+        return _className;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Class<? extends T> getHeldClass()
+    {
+        return _class;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getDisplayName()
+    {
+        return _displayName;
+    }
+
+    /* ---------------------------------------------------------------- */
+    public String getInitParameter(String param)
+    {
+        if (_initParams==null)
+            return null;
+        return (String)_initParams.get(param);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Enumeration getInitParameterNames()
+    {
+        if (_initParams==null)
+            return Collections.enumeration(Collections.EMPTY_LIST);
+        return Collections.enumeration(_initParams.keySet());
+    }
+
+    /* ---------------------------------------------------------------- */
+    public Map<String,String> getInitParameters()
+    {
+        return _initParams;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getName()
+    {
+        return _name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    public ServletHandler getServletHandler()
+    {
+        return _servletHandler;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void destroyInstance(Object instance)
+    throws Exception
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param className The className to set.
+     */
+    public void setClassName(String className)
+    {
+        _className = className;
+        _class=null;
+        if (_name==null)
+            _name=className+"-"+Integer.toHexString(this.hashCode());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param held The class to hold
+     */
+    public void setHeldClass(Class<? extends T> held)
+    {
+        _class=held;
+        if (held!=null)
+        {
+            _className=held.getName();
+            if (_name==null)
+                _name=held.getName()+"-"+Integer.toHexString(this.hashCode());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setDisplayName(String name)
+    {
+        _displayName=name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setInitParameter(String param,String value)
+    {
+        _initParams.put(param,value);
+    }
+    
+    /* ---------------------------------------------------------------- */
+    public void setInitParameters(Map<String,String> map)
+    {
+        _initParams.clear();
+        _initParams.putAll(map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * The name is a primary key for the held object.
+     * Ensure that the name is set BEFORE adding a Holder
+     * (eg ServletHolder or FilterHolder) to a ServletHandler.
+     * @param name The name to set.
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The {@link ServletHandler} that will handle requests dispatched to this servlet.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        _servletHandler = servletHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAsyncSupported(boolean suspendable)
+    {
+        _asyncSupported=suspendable;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isAsyncSupported()
+    {
+        return _asyncSupported;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void illegalStateIfContextStarted()
+    {
+        if (_servletHandler!=null)
+        {
+            ContextHandler.Context context=(ContextHandler.Context)_servletHandler.getServletContext();
+            if (context!=null && context.getContextHandler().isStarted())
+                throw new IllegalStateException("Started");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(_name).append("==").append(_className)
+        .append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
+        AggregateLifeCycle.dump(out,indent,_initParams.entrySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class HolderConfig 
+    {   
+        
+        /* -------------------------------------------------------- */
+        public ServletContext getServletContext()
+        {
+            return _servletHandler.getServletContext();
+        }
+
+        /* -------------------------------------------------------- */
+        public String getInitParameter(String param)
+        {
+            return Holder.this.getInitParameter(param);
+        }
+    
+        /* -------------------------------------------------------- */
+        public Enumeration getInitParameterNames()
+        {
+            return Holder.this.getInitParameterNames();
+        }
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    protected class HolderRegistration implements Registration.Dynamic
+    {
+        public void setAsyncSupported(boolean isAsyncSupported)
+        {
+            illegalStateIfContextStarted();
+            Holder.this.setAsyncSupported(isAsyncSupported);
+        }
+
+        public void setDescription(String description)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug(this+" is "+description);
+        }
+
+        public String getClassName()
+        {
+            return Holder.this.getClassName();
+        }
+
+        public String getInitParameter(String name)
+        {
+            return Holder.this.getInitParameter(name);
+        }
+
+        public Map<String, String> getInitParameters()
+        {
+            return Holder.this.getInitParameters();
+        }
+
+        public String getName()
+        {
+            return Holder.this.getName();
+        }
+
+        public boolean setInitParameter(String name, String value)
+        {
+            illegalStateIfContextStarted();
+            if (name == null) {
+                throw new IllegalArgumentException("init parameter name required");
+            }
+            if (value == null) {
+                throw new IllegalArgumentException("non-null value required for init parameter " + name);
+            }
+            if (Holder.this.getInitParameter(name)!=null)
+                return false;
+            Holder.this.setInitParameter(name,value);
+            return true;
+        }
+
+        public Set<String> setInitParameters(Map<String, String> initParameters)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (Map.Entry<String, String> entry : initParameters.entrySet())
+            {
+                if (entry.getKey() == null) {
+                    throw new IllegalArgumentException("init parameter name required");
+                }
+                if (entry.getValue() == null) {
+                    throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey());
+                }
+                if (Holder.this.getInitParameter(entry.getKey())!=null)
+                {
+                    if (clash==null)
+                        clash=new HashSet<String>();
+                    clash.add(entry.getKey());
+                }
+            }
+            if (clash!=null)
+                return clash;
+            Holder.this.getInitParameters().putAll(initParameters);
+            return Collections.emptySet();
+        }
+        
+        
+    }
+}
+
+
+
+
+
diff --git a/src/java/org/eclipse/jetty/servlet/Invoker.java b/src/java/org/eclipse/jetty/servlet/Invoker.java
new file mode 100644
index 0000000..c3e55f6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/Invoker.java
@@ -0,0 +1,314 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**  Dynamic Servlet Invoker.  
+ * This servlet invokes anonymous servlets that have not been defined   
+ * in the web.xml or by other means. The first element of the pathInfo  
+ * of a request passed to the envoker is treated as a servlet name for  
+ * an existing servlet, or as a class name of a new servlet.            
+ * This servlet is normally mapped to /servlet/*                        
+ * This servlet support the following initParams:                       
+ * <PRE>                                                                     
+ *  nonContextServlets       If false, the invoker can only load        
+ *                           servlets from the contexts classloader.    
+ *                           This is false by default and setting this  
+ *                           to true may have security implications.    
+ *                                                                      
+ *  verbose                  If true, log dynamic loads                 
+ *                                                                      
+ *  *                        All other parameters are copied to the     
+ *                           each dynamic servlet as init parameters    
+ * </PRE>
+ * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
+ * 
+ */
+public class Invoker extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(Invoker.class);
+
+
+    private ContextHandler _contextHandler;
+    private ServletHandler _servletHandler;
+    private Map.Entry _invokerEntry;
+    private Map _parameters;
+    private boolean _nonContextServlets;
+    private boolean _verbose;
+        
+    /* ------------------------------------------------------------ */
+    public void init()
+    {
+        ServletContext config=getServletContext();
+        _contextHandler=((ContextHandler.Context)config).getContextHandler();
+
+        Handler handler=_contextHandler.getHandler();
+        while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper))
+            handler=((HandlerWrapper)handler).getHandler();
+        _servletHandler = (ServletHandler)handler;
+        Enumeration e = getInitParameterNames();
+        while(e.hasMoreElements())
+        {
+            String param=(String)e.nextElement();
+            String value=getInitParameter(param);
+            String lvalue=value.toLowerCase(Locale.ENGLISH);
+            if ("nonContextServlets".equals(param))
+            {
+                _nonContextServlets=value.length()>0 && lvalue.startsWith("t");
+            }
+            if ("verbose".equals(param))
+            {
+                _verbose=value.length()>0 && lvalue.startsWith("t");
+            }
+            else
+            {
+                if (_parameters==null)
+                    _parameters=new HashMap();
+                _parameters.put(param,value);
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void service(HttpServletRequest request, HttpServletResponse response)
+	throws ServletException, IOException
+    {
+        // Get the requested path and info
+        boolean included=false;
+        String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+        if (servlet_path==null)
+            servlet_path=request.getServletPath();
+        else
+            included=true;
+        String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+        if (path_info==null)
+            path_info=request.getPathInfo();
+        
+        // Get the servlet class
+        String servlet = path_info;
+        if (servlet==null || servlet.length()<=1 )
+        {
+            response.sendError(404);
+            return;
+        }
+        
+        
+        int i0=servlet.charAt(0)=='/'?1:0;
+        int i1=servlet.indexOf('/',i0);
+        servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1);
+
+        // look for a named holder
+        ServletHolder[] holders = _servletHandler.getServlets();
+        ServletHolder holder = getHolder (holders, servlet);
+       
+        if (holder!=null)
+        {
+            // Found a named servlet (from a user's web.xml file) so
+            // now we add a mapping for it
+            if (LOG.isDebugEnabled())
+                LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*");
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet);
+            mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*");
+            _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class));
+        }
+        else
+        {
+            // look for a class mapping
+            if (servlet.endsWith(".class"))
+                servlet=servlet.substring(0,servlet.length()-6);
+            if (servlet==null || servlet.length()==0)
+            {
+                response.sendError(404);
+                return;
+            }   
+        
+            synchronized(_servletHandler)
+            {
+                // find the entry for the invoker (me)
+                 _invokerEntry=_servletHandler.getHolderEntry(servlet_path);
+            
+                // Check for existing mapping (avoid threaded race).
+                String path=URIUtil.addPaths(servlet_path,servlet);
+                Map.Entry entry = _servletHandler.getHolderEntry(path);
+               
+                if (entry!=null && !entry.equals(_invokerEntry))
+                {
+                    // Use the holder
+                    holder=(ServletHolder)entry.getValue();
+                }
+                else
+                {
+                    // Make a holder
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Making new servlet="+servlet+" with path="+path+"/*");
+                    holder=_servletHandler.addServletWithMapping(servlet, path+"/*");
+                    
+                    if (_parameters!=null)
+                        holder.setInitParameters(_parameters);
+                    
+                    try {holder.start();}
+                    catch (Exception e)
+                    {
+                        LOG.debug(e);
+                        throw new UnavailableException(e.toString());
+                    }
+                    
+                    // Check it is from an allowable classloader
+                    if (!_nonContextServlets)
+                    {
+                        Object s=holder.getServlet();
+                        
+                        if (_contextHandler.getClassLoader()!=
+                            s.getClass().getClassLoader())
+                        {
+                            try 
+                            {
+                                holder.stop();
+                            } 
+                            catch (Exception e) 
+                            {
+                                LOG.ignore(e);
+                            }
+                            
+                            LOG.warn("Dynamic servlet "+s+
+                                         " not loaded from context "+
+                                         request.getContextPath());
+                            throw new UnavailableException("Not in context");
+                        }
+                    }
+
+                    if (_verbose && LOG.isDebugEnabled())
+                        LOG.debug("Dynamic load '"+servlet+"' at "+path);
+                }
+            }
+        }
+        
+        if (holder!=null)
+        {
+            final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+            holder.handle(baseRequest,
+                    new InvokedRequest(request,included,servlet,servlet_path,path_info),
+                          response);
+        }
+        else
+        {
+            LOG.info("Can't find holder for servlet: "+servlet);
+            response.sendError(404);
+        }
+            
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    class InvokedRequest extends HttpServletRequestWrapper
+    {
+        String _servletPath;
+        String _pathInfo;
+        boolean _included;
+        
+        /* ------------------------------------------------------------ */
+        InvokedRequest(HttpServletRequest request,
+                boolean included,
+                String name,
+                String servletPath,
+                String pathInfo)
+        {
+            super(request);
+            _included=included;
+            _servletPath=URIUtil.addPaths(servletPath,name);
+            _pathInfo=pathInfo.substring(name.length()+1);
+            if (_pathInfo.length()==0)
+                _pathInfo=null;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public String getServletPath()
+        {
+            if (_included)
+                return super.getServletPath();
+            return _servletPath;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public String getPathInfo()
+        {
+            if (_included)
+                return super.getPathInfo();
+            return _pathInfo;
+        }
+        
+        /* ------------------------------------------------------------ */
+        public Object getAttribute(String name)
+        {
+            if (_included)
+            {
+                if (name.equals(Dispatcher.INCLUDE_REQUEST_URI))
+                    return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo);
+                if (name.equals(Dispatcher.INCLUDE_PATH_INFO))
+                    return _pathInfo;
+                if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH))
+                    return _servletPath;
+            }
+            return super.getAttribute(name);
+        }
+    }
+    
+    
+    private ServletHolder getHolder(ServletHolder[] holders, String servlet)
+    {
+        if (holders == null)
+            return null;
+       
+        ServletHolder holder = null;
+        for (int i=0; holder==null && i<holders.length; i++)
+        {
+            if (holders[i].getName().equals(servlet))
+            {
+                holder = holders[i];
+            }
+        }
+        return holder;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java b/src/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
new file mode 100644
index 0000000..f2ec2f3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/JspPropertyGroupServlet.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.GenericServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet handling JSP Property Group mappings
+ * <p>
+ * This servlet is mapped to by any URL pattern for a JSP property group. 
+ * Resources handled by this servlet that are not directories will be passed
+ * directly to the JSP servlet.    Resources that are directories will be 
+ * passed directly to the default servlet.
+ */
+public class JspPropertyGroupServlet extends GenericServlet
+{
+    private static final long serialVersionUID = 3681783214726776945L;
+    
+    public final static String NAME = "__org.eclipse.jetty.servlet.JspPropertyGroupServlet__";
+    private final ServletHandler _servletHandler;
+    private final ContextHandler _contextHandler;
+    private ServletHolder _dftServlet;
+    private ServletHolder _jspServlet;
+    private boolean _starJspMapped;
+    
+    public JspPropertyGroupServlet(ContextHandler context, ServletHandler servletHandler)
+    {
+        _contextHandler=context;
+        _servletHandler=servletHandler;        
+    }
+    
+    @Override
+    public void init() throws ServletException
+    {            
+        String jsp_name = "jsp";
+        ServletMapping servlet_mapping =_servletHandler.getServletMapping("*.jsp");
+        if (servlet_mapping!=null)
+        {
+            _starJspMapped=true;
+           
+            //now find the jsp servlet, ignoring the mapping that is for ourself
+            ServletMapping[] mappings = _servletHandler.getServletMappings();
+            for (ServletMapping m:mappings)
+            {
+                String[] paths = m.getPathSpecs();
+                if (paths!=null)
+                {
+                    for (String path:paths)
+                    {
+                        if ("*.jsp".equals(path) && !NAME.equals(m.getServletName()))
+                            servlet_mapping = m;
+                    }
+                }
+            }
+            
+            jsp_name=servlet_mapping.getServletName();
+        }
+        _jspServlet=_servletHandler.getServlet(jsp_name);
+        
+        String dft_name="default";
+        ServletMapping default_mapping=_servletHandler.getServletMapping("/");
+        if (default_mapping!=null)
+            dft_name=default_mapping.getServletName();
+        _dftServlet=_servletHandler.getServlet(dft_name);
+    }
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+    {           
+        HttpServletRequest request = null;
+        if (req instanceof HttpServletRequest)
+            request = (HttpServletRequest)req;
+        else
+            throw new ServletException("Request not HttpServletRequest");
+
+        String servletPath=null;
+        String pathInfo=null;
+        if (request.getAttribute(Dispatcher.INCLUDE_REQUEST_URI)!=null)
+        {
+            servletPath=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH);
+            pathInfo=(String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO);
+            if (servletPath==null)
+            {
+                servletPath=request.getServletPath();
+                pathInfo=request.getPathInfo();
+            }
+        }
+        else
+        {
+            servletPath = request.getServletPath();
+            pathInfo = request.getPathInfo();
+        }
+        
+        String pathInContext=URIUtil.addPaths(servletPath,pathInfo);
+        
+        if (pathInContext.endsWith("/"))
+        {
+            _dftServlet.getServlet().service(req,res);
+        }
+        else if (_starJspMapped && pathInContext.toLowerCase().endsWith(".jsp"))
+        {
+            _jspServlet.getServlet().service(req,res);
+        }
+        else
+        {
+         
+            Resource resource = _contextHandler.getResource(pathInContext);
+            if (resource!=null && resource.isDirectory())
+                _dftServlet.getServlet().service(req,res);
+            else
+                _jspServlet.getServlet().service(req,res);
+        }
+        
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlet/NoJspServlet.java b/src/java/org/eclipse/jetty/servlet/NoJspServlet.java
new file mode 100644
index 0000000..8a9ff5b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/NoJspServlet.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class NoJspServlet extends HttpServlet
+{
+    private boolean _warned;
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (!_warned)
+            getServletContext().log("No JSP support.  Check that JSP jars are in lib/jsp and that the JSP option has been specified to start.jar");
+        _warned=true;
+
+        response.sendError(500,"JSP support not configured");
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlet/ServletContextHandler.java b/src/java/org/eclipse/jetty/servlet/ServletContextHandler.java
new file mode 100644
index 0000000..314b321
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/ServletContextHandler.java
@@ -0,0 +1,1292 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterRegistration;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SessionCookieConfig;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.security.Constraint;
+
+
+/* ------------------------------------------------------------ */
+/** Servlet Context.
+ * This extension to the ContextHandler allows for
+ * simple construction of a context with ServletHandler and optionally
+ * session and security handlers, et.<pre>
+ *   new ServletContext("/context",Context.SESSIONS|Context.NO_SECURITY);
+ * </pre>
+ * <p/>
+ * This class should have been called ServletContext, but this would have
+ * cause confusion with {@link ServletContext}.
+ */
+public class ServletContextHandler extends ContextHandler
+{   
+    public final static int SESSIONS=1;
+    public final static int SECURITY=2;
+    public final static int NO_SESSIONS=0;
+    public final static int NO_SECURITY=0;
+
+    protected final List<Decorator> _decorators= new ArrayList<Decorator>();
+    protected Class<? extends SecurityHandler> _defaultSecurityHandlerClass=org.eclipse.jetty.security.ConstraintSecurityHandler.class;
+    protected SessionHandler _sessionHandler;
+    protected SecurityHandler _securityHandler;
+    protected ServletHandler _servletHandler;
+    protected HandlerWrapper _wrapper;
+    protected int _options;
+    protected JspConfigDescriptor _jspConfig;
+    protected Object _restrictedContextListeners;
+    private boolean _restrictListeners = true;
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler()
+    {
+        this(null,null,null,null,null);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(int options)
+    {
+        this(null,null,options);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath)
+    {
+        this(parent,contextPath,null,null,null,null);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, int options)
+    {
+        this(parent,contextPath,null,null,null,null);
+        _options=options;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, boolean sessions, boolean security)
+    {
+        this(parent,contextPath,(sessions?SESSIONS:0)|(security?SECURITY:0));
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {   
+        this(parent,null,sessionHandler,securityHandler,servletHandler,errorHandler);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletContextHandler(HandlerContainer parent, String contextPath, SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler)
+    {   
+        super((ContextHandler.Context)null);
+        _scontext = new Context();
+        _sessionHandler = sessionHandler;
+        _securityHandler = securityHandler;
+        _servletHandler = servletHandler;
+            
+        if (errorHandler!=null)
+            setErrorHandler(errorHandler);
+
+        if (contextPath!=null)
+            setContextPath(contextPath);
+
+        if (parent instanceof HandlerWrapper)
+            ((HandlerWrapper)parent).setHandler(this);
+        else if (parent instanceof HandlerCollection)
+            ((HandlerCollection)parent).addHandler(this);
+    }    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.handler.ContextHandler#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        if (_decorators != null)
+            _decorators.clear();
+        if (_wrapper != null)
+            _wrapper.setHandler(null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the defaultSecurityHandlerClass.
+     * @return the defaultSecurityHandlerClass
+     */
+    public Class<? extends SecurityHandler> getDefaultSecurityHandlerClass()
+    {
+        return _defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the defaultSecurityHandlerClass.
+     * @param defaultSecurityHandlerClass the defaultSecurityHandlerClass to set
+     */
+    public void setDefaultSecurityHandlerClass(Class<? extends SecurityHandler> defaultSecurityHandlerClass)
+    {
+        _defaultSecurityHandlerClass = defaultSecurityHandlerClass;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected SessionHandler newSessionHandler()
+    {
+        return new SessionHandler();
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected SecurityHandler newSecurityHandler()
+    {
+        try
+        {
+            return (SecurityHandler)_defaultSecurityHandlerClass.newInstance();
+        }
+        catch(Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected ServletHandler newServletHandler()
+    {
+        return new ServletHandler();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Finish constructing handlers and link them together.
+     * 
+     * @see org.eclipse.jetty.server.handler.ContextHandler#startContext()
+     */
+    protected void startContext() throws Exception
+    {
+        // force creation of missing handlers.
+        getSessionHandler();
+        getSecurityHandler();
+        getServletHandler();
+        
+        Handler handler = _servletHandler;
+        if (_securityHandler!=null)
+        {
+            _securityHandler.setHandler(handler);
+            handler=_securityHandler;
+        }
+        
+        if (_sessionHandler!=null)
+        {
+            _sessionHandler.setHandler(handler);
+            handler=_sessionHandler;
+        }
+        
+        // skip any wrapped handlers 
+        _wrapper=this;
+        while (_wrapper!=handler && _wrapper.getHandler() instanceof HandlerWrapper)
+            _wrapper=(HandlerWrapper)_wrapper.getHandler();
+        
+        // if we are not already linked
+        if (_wrapper!=handler)
+        {
+            if (_wrapper.getHandler()!=null )
+                throw new IllegalStateException("!ScopedHandler");
+            _wrapper.setHandler(handler);
+        }
+        
+    	super.startContext();
+
+    	// OK to Initialize servlet handler now
+    	if (_servletHandler != null && _servletHandler.isStarted())
+    	{
+    	    for (int i=_decorators.size()-1;i>=0; i--)
+    	    {
+    	        Decorator decorator = _decorators.get(i);
+                if (_servletHandler.getFilters()!=null)
+                    for (FilterHolder holder:_servletHandler.getFilters())
+                        decorator.decorateFilterHolder(holder);
+    	        if(_servletHandler.getServlets()!=null)
+    	            for (ServletHolder holder:_servletHandler.getServlets())
+    	                decorator.decorateServletHolder(holder);
+    	    }   
+    	        
+    	    _servletHandler.initialize();
+    	}
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the securityHandler.
+     */
+    public SecurityHandler getSecurityHandler()
+    {
+        if (_securityHandler==null && (_options&SECURITY)!=0 && !isStarted()) 
+            _securityHandler=newSecurityHandler();
+        
+        return _securityHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletHandler.
+     */
+    public ServletHandler getServletHandler()
+    {
+        if (_servletHandler==null && !isStarted()) 
+            _servletHandler=newServletHandler();
+        return _servletHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the sessionHandler.
+     */
+    public SessionHandler getSessionHandler()
+    {
+        if (_sessionHandler==null && (_options&SESSIONS)!=0 && !isStarted()) 
+            _sessionHandler=newSessionHandler();
+        return _sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(String className,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(className, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public ServletHolder addServlet(Class<? extends Servlet> servlet,String pathSpec)
+    {
+        return getServletHandler().addServletWithMapping(servlet.getName(), pathSpec);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     */
+    public void addServlet(ServletHolder servlet,String pathSpec)
+    {
+        getServletHandler().addServletWithMapping(servlet, pathSpec);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a filter
+     */
+    public void addFilter(FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        getServletHandler().addFilterWithMapping(holder,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(Class<? extends Filter> filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** convenience method to add a filter
+     */
+    public FilterHolder addFilter(String filterClass,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return getServletHandler().addFilterWithMapping(filterClass,pathSpec,dispatches);
+    }
+
+    /**
+     * notification that a ServletRegistration has been created so we can track the annotations
+     * @param holder new holder created through the api.
+     * @return the ServletRegistration.Dynamic
+     */
+    protected ServletRegistration.Dynamic dynamicHolderAdded(ServletHolder holder) {
+        return holder.getRegistration();
+    }
+
+    /**
+     * delegate for ServletContext.declareRole method
+     * @param roleNames role names to add
+     */
+    protected void addRoles(String... roleNames) {
+        //Get a reference to the SecurityHandler, which must be ConstraintAware
+        if (_securityHandler != null && _securityHandler instanceof ConstraintAware)
+        {
+            HashSet<String> union = new HashSet<String>();
+            Set<String> existing = ((ConstraintAware)_securityHandler).getRoles();
+            if (existing != null)
+                union.addAll(existing);
+            union.addAll(Arrays.asList(roleNames));
+            ((ConstraintSecurityHandler)_securityHandler).setRoles(union);
+        }
+    }
+
+    /**
+     * Delegate for ServletRegistration.Dynamic.setServletSecurity method
+     * @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on
+     * @param servletSecurityElement new security info
+     * @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml
+     * security constraints and thus will be unaffected by this call.
+     */
+    public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
+    {
+        //Default implementation is to just accept them all. If using a webapp, then this behaviour is overridden in WebAppContext.setServletSecurity       
+        Collection<String> pathSpecs = registration.getMappings();
+        if (pathSpecs != null)
+        {
+            for (String pathSpec:pathSpecs)
+            {
+                List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+                for (ConstraintMapping m:mappings)
+                    ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+            }
+        }
+        return Collections.emptySet();
+    }
+
+
+    
+    public void restrictEventListener (EventListener e)
+    {
+        if (_restrictListeners && e instanceof ServletContextListener)
+            _restrictedContextListeners = LazyList.add(_restrictedContextListeners, e);
+    }
+
+    public boolean isRestrictListeners() {
+        return _restrictListeners;
+    }
+
+    public void setRestrictListeners(boolean restrictListeners) {
+        this._restrictListeners = restrictListeners;
+    }
+
+    public void callContextInitialized(ServletContextListener l, ServletContextEvent e)
+    {
+        try
+        {
+            //toggle state of the dynamic API so that the listener cannot use it
+            if (LazyList.contains(_restrictedContextListeners, l))
+                this.getServletContext().setEnabled(false);
+            
+            super.callContextInitialized(l, e);
+        }
+        finally
+        {
+            //untoggle the state of the dynamic API
+            this.getServletContext().setEnabled(true);
+        }
+    }
+
+    
+    public void callContextDestroyed(ServletContextListener l, ServletContextEvent e)
+    {
+        super.callContextDestroyed(l, e);
+    }
+
+  
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sessionHandler The sessionHandler to set.
+     */
+    public void setSessionHandler(SessionHandler sessionHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        _sessionHandler = sessionHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param securityHandler The {@link SecurityHandler} to set on this context.
+     */
+    public void setSecurityHandler(SecurityHandler securityHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        _securityHandler = securityHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletHandler The servletHandler to set.
+     */
+    public void setServletHandler(ServletHandler servletHandler)
+    {
+        if (isStarted())
+            throw new IllegalStateException("STARTED");
+
+        _servletHandler = servletHandler;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The decorator list used to resource inject new Filters, Servlets and EventListeners
+     */
+    public List<Decorator> getDecorators()
+    {
+        return Collections.unmodifiableList(_decorators);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorators The lis of {@link Decorator}s
+     */
+    public void setDecorators(List<Decorator> decorators)
+    {
+        _decorators.clear();
+        _decorators.addAll(decorators);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param decorator The decorator to add
+     */
+    public void addDecorator(Decorator decorator)
+    {
+        _decorators.add(decorator);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroyServletInstance(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        for (Decorator decorator : _decorators)
+            decorator.destroyFilterInstance(filter);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static class JspPropertyGroup implements JspPropertyGroupDescriptor
+    {
+        private List<String> _urlPatterns = new ArrayList<String>();
+        private String _elIgnored;
+        private String _pageEncoding;
+        private String _scriptingInvalid;
+        private String _isXml;
+        private List<String> _includePreludes = new ArrayList<String>();
+        private List<String> _includeCodas = new ArrayList<String>();
+        private String _deferredSyntaxAllowedAsLiteral;
+        private String _trimDirectiveWhitespaces;
+        private String _defaultContentType;
+        private String _buffer;
+        private String _errorOnUndeclaredNamespace;
+        
+        
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getUrlPatterns()
+         */
+        public Collection<String> getUrlPatterns()
+        {
+            return new ArrayList<String>(_urlPatterns); // spec says must be a copy
+        }
+        
+        public void addUrlPattern (String s)
+        {
+            if (!_urlPatterns.contains(s))
+                _urlPatterns.add(s);
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getElIgnored()
+         */
+        public String getElIgnored()
+        {
+            return _elIgnored;
+        }
+        
+        public void setElIgnored (String s)
+        {
+            _elIgnored = s;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getPageEncoding()
+         */
+        public String getPageEncoding()
+        {
+            return _pageEncoding;
+        }
+        
+        public void setPageEncoding(String pageEncoding)
+        {
+            _pageEncoding = pageEncoding;
+        }
+
+        public void setScriptingInvalid(String scriptingInvalid)
+        {
+            _scriptingInvalid = scriptingInvalid;
+        }
+
+        public void setIsXml(String isXml)
+        {
+            _isXml = isXml;
+        }
+
+        public void setDeferredSyntaxAllowedAsLiteral(String deferredSyntaxAllowedAsLiteral)
+        {
+            _deferredSyntaxAllowedAsLiteral = deferredSyntaxAllowedAsLiteral;
+        }
+
+        public void setTrimDirectiveWhitespaces(String trimDirectiveWhitespaces)
+        {
+            _trimDirectiveWhitespaces = trimDirectiveWhitespaces;
+        }
+
+        public void setDefaultContentType(String defaultContentType)
+        {
+            _defaultContentType = defaultContentType;
+        }
+
+        public void setBuffer(String buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void setErrorOnUndeclaredNamespace(String errorOnUndeclaredNamespace)
+        {
+            _errorOnUndeclaredNamespace = errorOnUndeclaredNamespace;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getScriptingInvalid()
+         */
+        public String getScriptingInvalid()
+        {
+            return _scriptingInvalid;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIsXml()
+         */
+        public String getIsXml()
+        {
+            return _isXml;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludePreludes()
+         */
+        public Collection<String> getIncludePreludes()
+        {
+            return new ArrayList<String>(_includePreludes); //must be a copy
+        }
+        
+        public void addIncludePrelude(String prelude)
+        {
+            if (!_includePreludes.contains(prelude))
+                _includePreludes.add(prelude);
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getIncludeCodas()
+         */
+        public Collection<String> getIncludeCodas()
+        {
+            return new ArrayList<String>(_includeCodas); //must be a copy
+        }
+
+        public void addIncludeCoda (String coda)
+        {
+            if (!_includeCodas.contains(coda))
+                _includeCodas.add(coda);
+        }
+        
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDeferredSyntaxAllowedAsLiteral()
+         */
+        public String getDeferredSyntaxAllowedAsLiteral()
+        {
+            return _deferredSyntaxAllowedAsLiteral;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getTrimDirectiveWhitespaces()
+         */
+        public String getTrimDirectiveWhitespaces()
+        {
+            return _trimDirectiveWhitespaces;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getDefaultContentType()
+         */
+        public String getDefaultContentType()
+        {
+            return _defaultContentType;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getBuffer()
+         */
+        public String getBuffer()
+        {
+            return _buffer;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspPropertyGroupDescriptor#getErrorOnUndeclaredNamespace()
+         */
+        public String getErrorOnUndeclaredNamespace()
+        {
+            return _errorOnUndeclaredNamespace;
+        }
+        
+        public String toString ()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspPropertyGroupDescriptor:");
+            sb.append(" el-ignored="+_elIgnored);
+            sb.append(" is-xml="+_isXml);
+            sb.append(" page-encoding="+_pageEncoding);
+            sb.append(" scripting-invalid="+_scriptingInvalid);
+            sb.append(" deferred-syntax-allowed-as-literal="+_deferredSyntaxAllowedAsLiteral);
+            sb.append(" trim-directive-whitespaces"+_trimDirectiveWhitespaces);
+            sb.append(" default-content-type="+_defaultContentType);
+            sb.append(" buffer="+_buffer);
+            sb.append(" error-on-undeclared-namespace="+_errorOnUndeclaredNamespace);
+            for (String prelude:_includePreludes)
+                sb.append(" include-prelude="+prelude);
+            for (String coda:_includeCodas)
+                sb.append(" include-coda="+coda);
+            return sb.toString();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static class TagLib implements TaglibDescriptor
+    {
+        private String _uri;
+        private String _location;
+
+        /** 
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibURI()
+         */
+        public String getTaglibURI()
+        {
+           return _uri;
+        }
+        
+        public void setTaglibURI(String uri)
+        {
+            _uri = uri;
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.TaglibDescriptor#getTaglibLocation()
+         */
+        public String getTaglibLocation()
+        {
+            return _location;
+        }
+        
+        public void setTaglibLocation(String location)
+        {
+            _location = location;
+        }
+    
+        public String toString()
+        {
+            return ("TagLibDescriptor: taglib-uri="+_uri+" location="+_location);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static class JspConfig implements JspConfigDescriptor
+    {
+        private List<TaglibDescriptor> _taglibs = new ArrayList<TaglibDescriptor>();
+        private List<JspPropertyGroupDescriptor> _jspPropertyGroups = new ArrayList<JspPropertyGroupDescriptor>();
+        
+        public JspConfig() {}
+
+        /** 
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getTaglibs()
+         */
+        public Collection<TaglibDescriptor> getTaglibs()
+        {
+            return new ArrayList<TaglibDescriptor>(_taglibs);
+        }
+        
+        public void addTaglibDescriptor (TaglibDescriptor d)
+        {
+            _taglibs.add(d);
+        }
+
+        /** 
+         * @see javax.servlet.descriptor.JspConfigDescriptor#getJspPropertyGroups()
+         */
+        public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups()
+        {
+           return new ArrayList<JspPropertyGroupDescriptor>(_jspPropertyGroups);
+        }
+        
+        public void addJspPropertyGroup(JspPropertyGroupDescriptor g)
+        {
+            _jspPropertyGroups.add(g);
+        }
+        
+        public String toString()
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append("JspConfigDescriptor: \n");
+            for (TaglibDescriptor taglib:_taglibs)
+                sb.append(taglib+"\n");
+            for (JspPropertyGroupDescriptor jpg:_jspPropertyGroups)
+                sb.append(jpg+"\n");
+            return sb.toString();
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public class Context extends ContextHandler.Context
+    {
+        /* ------------------------------------------------------------ */
+        /* 
+         * @see javax.servlet.ServletContext#getNamedDispatcher(java.lang.String)
+         */
+        @Override
+        public RequestDispatcher getNamedDispatcher(String name)
+        {
+            ContextHandler context=org.eclipse.jetty.servlet.ServletContextHandler.this;
+            if (_servletHandler==null)
+                return null;
+            ServletHolder holder = _servletHandler.getServlet(name);
+            if (holder==null || !holder.isEnabled())
+                return null;
+            return new Dispatcher(context, name);
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Class<? extends Filter> filterClass)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setHeldClass(filterClass);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setHeldClass(filterClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, String className)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setClassName(className);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setClassName(className);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public FilterRegistration.Dynamic addFilter(String filterName, Filter filter)
+        {
+            if (isStarted())
+                throw new IllegalStateException();
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            FilterHolder holder = handler.getFilter(filterName);
+            if (holder == null)
+            {
+                //new filter
+                holder = handler.newFilterHolder(Holder.Source.JAVAX_API);
+                holder.setName(filterName);
+                holder.setFilter(filter);
+                handler.addFilter(holder);
+                return holder.getRegistration();
+            }
+            
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                //preliminary filter registration completion
+                holder.setFilter(filter);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing filter
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Class<? extends Servlet> servletClass)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setHeldClass(servletClass);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setHeldClass(servletClass);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name      
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, String className)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();            
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                //new servlet
+                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setClassName(className);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setClassName(className); 
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @since servlet-api-3.0
+         */
+        @Override
+        public ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+
+            final ServletHandler handler = ServletContextHandler.this.getServletHandler();
+            ServletHolder holder = handler.getServlet(servletName);
+            if (holder == null)
+            {
+                holder = handler.newServletHolder(Holder.Source.JAVAX_API);
+                holder.setName(servletName);
+                holder.setServlet(servlet);
+                handler.addServlet(holder);
+                return dynamicHolderAdded(holder);
+            }
+            
+            //complete a partial registration
+            if (holder.getClassName()==null && holder.getHeldClass()==null)
+            {
+                holder.setServlet(servlet);
+                return holder.getRegistration();
+            }
+            else
+                return null; //existing completed registration for servlet name
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean setInitParameter(String name, String value)
+        {
+            // TODO other started conditions
+            if (!isStarting())
+                throw new IllegalStateException();
+            
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            return super.setInitParameter(name,value);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Filter> T createFilter(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T f = c.newInstance();
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    f=decorator.decorateFilterInstance(f);
+                }
+                return f;
+            }
+            catch (InstantiationException e)
+            {
+                throw new ServletException(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public <T extends Servlet> T createServlet(Class<T> c) throws ServletException
+        {
+            try
+            {
+                T s = c.newInstance();
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    s=decorator.decorateServletInstance(s);
+                }
+                return s;
+            }
+            catch (InstantiationException e)
+            {
+                throw new ServletException(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getDefaultSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getDefaultSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public Set<SessionTrackingMode> getEffectiveSessionTrackingModes()
+        {
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getEffectiveSessionTrackingModes();
+            return null;
+        }
+
+        @Override
+        public FilterRegistration getFilterRegistration(String filterName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            final FilterHolder holder=ServletContextHandler.this.getServletHandler().getFilter(filterName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends FilterRegistration> getFilterRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            HashMap<String, FilterRegistration> registrations = new HashMap<String, FilterRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            FilterHolder[] holders=handler.getFilters();
+            if (holders!=null)
+            {
+                for (FilterHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public ServletRegistration getServletRegistration(String servletName)
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            final ServletHolder holder=ServletContextHandler.this.getServletHandler().getServlet(servletName);
+            return (holder==null)?null:holder.getRegistration();
+        }
+
+        @Override
+        public Map<String, ? extends ServletRegistration> getServletRegistrations()
+        {
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            HashMap<String, ServletRegistration> registrations = new HashMap<String, ServletRegistration>();
+            ServletHandler handler=ServletContextHandler.this.getServletHandler();
+            ServletHolder[] holders=handler.getServlets();
+            if (holders!=null)
+            {
+                for (ServletHolder holder : holders)
+                    registrations.put(holder.getName(),holder.getRegistration());
+            }
+            return registrations;
+        }
+
+        @Override
+        public SessionCookieConfig getSessionCookieConfig()
+        {
+            // TODO other started conditions
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            if (_sessionHandler!=null)
+                return _sessionHandler.getSessionManager().getSessionCookieConfig();
+            return null;
+        }
+
+        @Override
+        public void setSessionTrackingModes(Set<SessionTrackingMode> sessionTrackingModes)
+        {
+            // TODO other started conditions
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            
+            
+            if (_sessionHandler!=null)
+                _sessionHandler.getSessionManager().setSessionTrackingModes(sessionTrackingModes);
+        }
+
+        @Override
+        public void addListener(String className)
+        {
+            // TODO other started conditions
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(className);
+        }
+
+        @Override
+        public <T extends EventListener> void addListener(T t)
+        {
+            // TODO other started conditions
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(t);
+        }
+
+        @Override
+        public void addListener(Class<? extends EventListener> listenerClass)
+        {
+            // TODO other started conditions
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            super.addListener(listenerClass);
+        }
+
+        @Override
+        public <T extends EventListener> T createListener(Class<T> clazz) throws ServletException
+        {
+            try
+            {
+                T l = super.createListener(clazz);
+
+                for (int i=_decorators.size()-1; i>=0; i--)
+                {
+                    Decorator decorator = _decorators.get(i);
+                    l=decorator.decorateListenerInstance(l);
+                }
+                return l;
+            }
+            catch(ServletException e)
+            {
+                throw e;
+            }
+            catch(Exception e)
+            {
+                throw new ServletException(e);
+            }
+        }
+
+
+        @Override
+        public JspConfigDescriptor getJspConfigDescriptor()
+        {
+            return _jspConfig;
+        }
+        
+        @Override
+        public void setJspConfigDescriptor(JspConfigDescriptor d)
+        {
+            _jspConfig = d;
+        }
+        
+        
+        @Override
+        public void declareRoles(String... roleNames)
+        {
+            if (!isStarting())
+                throw new IllegalStateException();
+            if (!_enabled)
+                throw new UnsupportedOperationException();
+            addRoles(roleNames);
+
+
+        }
+
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /** Interface to decorate loaded classes.
+     */
+    public interface Decorator
+    {
+        <T extends Filter> T decorateFilterInstance(T filter) throws ServletException;
+        <T extends Servlet> T decorateServletInstance(T servlet) throws ServletException;
+        <T extends EventListener> T decorateListenerInstance(T listener) throws ServletException;
+
+        void decorateFilterHolder(FilterHolder filter) throws ServletException;
+        void decorateServletHolder(ServletHolder servlet) throws ServletException;
+        
+        void destroyServletInstance(Servlet s);
+        void destroyFilterInstance(Filter f);
+        void destroyListenerInstance(EventListener f);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/ServletHandler.java b/src/java/org/eclipse/jetty/servlet/ServletHandler.java
new file mode 100644
index 0000000..53da17a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/ServletHandler.java
@@ -0,0 +1,1649 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.continuation.ContinuationThrowable;
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.AsyncContinuation;
+import org.eclipse.jetty.server.Dispatcher;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServletRequestHttpWrapper;
+import org.eclipse.jetty.server.ServletResponseHttpWrapper;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ScopedHandler;
+import org.eclipse.jetty.servlet.Holder.Source;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* --------------------------------------------------------------------- */
+/** Servlet HttpHandler.
+ * This handler maps requests to servlets that implement the
+ * javax.servlet.http.HttpServlet API.
+ * <P>
+ * This handler does not implement the full J2EE features and is intended to
+ * be used directly when a full web application is not required.  If a Web application is required,
+ * then this handler should be used as part of a <code>org.eclipse.jetty.webapp.WebAppContext</code>.
+ * <p>
+ * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()}
+ * method must be called manually after start().
+ */
+public class ServletHandler extends ScopedHandler
+{
+    private static final Logger LOG = Log.getLogger(ServletHandler.class);
+
+    /* ------------------------------------------------------------ */
+    public static final String __DEFAULT_SERVLET="default";
+        
+    /* ------------------------------------------------------------ */
+    private ServletContextHandler _contextHandler;
+    private ContextHandler.Context _servletContext;
+    private FilterHolder[] _filters=new FilterHolder[0];
+    private FilterMapping[] _filterMappings;
+    private int _matchBeforeIndex = -1; //index of last programmatic FilterMapping with isMatchAfter=false
+    private int _matchAfterIndex = -1;  //index of 1st programmatic FilterMapping with isMatchAfter=true
+    private boolean _filterChainsCached=true;
+    private int _maxFilterChainsCacheSize=512;
+    private boolean _startWithUnavailable=false;
+    private IdentityService _identityService;
+    
+    private ServletHolder[] _servlets=new ServletHolder[0];
+    private ServletMapping[] _servletMappings;
+    
+    private final Map<String,FilterHolder> _filterNameMap= new HashMap<String,FilterHolder>();
+    private List<FilterMapping> _filterPathMappings;
+    private MultiMap<String> _filterNameMappings;
+    
+    private final Map<String,ServletHolder> _servletNameMap=new HashMap<String,ServletHolder>();
+    private PathMap _servletPathMap;
+    
+    protected final ConcurrentMap<String,FilterChain> _chainCache[] = new ConcurrentMap[FilterMapping.ALL];
+    protected final Queue<String>[] _chainLRU = new Queue[FilterMapping.ALL];
+
+
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     */
+    public ServletHandler()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.handler.AbstractHandler#setServer(org.eclipse.jetty.server.Server)
+     */
+    public void setServer(Server server)
+    {
+        Server old=getServer();
+        if (old!=null && old!=server)
+        {
+            getServer().getContainer().update(this, _filters, null, "filter",true);
+            getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
+            getServer().getContainer().update(this, _servlets, null, "servlet",true);
+            getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
+        }
+
+        super.setServer(server);
+        
+        if (server!=null && old!=server)
+        {
+            server.getContainer().update(this, null, _filters, "filter",true);
+            server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
+            server.getContainer().update(this, null, _servlets, "servlet",true);
+            server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
+        }
+    }
+
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStart()
+        throws Exception
+    {
+        _servletContext=ContextHandler.getCurrentContext();
+        _contextHandler=(ServletContextHandler)(_servletContext==null?null:_servletContext.getContextHandler());
+
+        if (_contextHandler!=null)
+        {
+            SecurityHandler security_handler = (SecurityHandler)_contextHandler.getChildHandlerByClass(SecurityHandler.class);
+            if (security_handler!=null)
+                _identityService=security_handler.getIdentityService();
+        }
+        
+        updateNameMappings();
+        updateMappings();
+        
+        if(_filterChainsCached)
+        {
+            _chainCache[FilterMapping.REQUEST]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.FORWARD]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.INCLUDE]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ERROR]=new ConcurrentHashMap<String,FilterChain>();
+            _chainCache[FilterMapping.ASYNC]=new ConcurrentHashMap<String,FilterChain>();
+            
+            _chainLRU[FilterMapping.REQUEST]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.FORWARD]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.INCLUDE]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ERROR]=new ConcurrentLinkedQueue<String>();
+            _chainLRU[FilterMapping.ASYNC]=new ConcurrentLinkedQueue<String>();
+        }
+
+        super.doStart();
+        
+        if (_contextHandler==null || !(_contextHandler instanceof ServletContextHandler))
+            initialize();
+    }   
+    
+    /* ----------------------------------------------------------------- */
+    @Override
+    protected synchronized void doStop()
+        throws Exception
+    {
+        super.doStop();
+
+        // Stop filters
+        List<FilterHolder> filterHolders = new ArrayList<FilterHolder>();
+        List<FilterMapping> filterMappings = LazyList.array2List(_filterMappings);
+        if (_filters!=null)
+        {
+            for (int i=_filters.length; i-->0;)
+            {
+                try { _filters[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
+                if (_filters[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove all of the mappings that were for non-embedded filters
+                    _filterNameMap.remove(_filters[i].getName());
+                    //remove any mappings associated with this filter
+                    ListIterator<FilterMapping> fmitor = filterMappings.listIterator();
+                    while (fmitor.hasNext())
+                    {
+                        FilterMapping fm = fmitor.next();
+                        if (fm.getFilterName().equals(_filters[i].getName()))
+                            fmitor.remove();
+                    }
+                }
+                else
+                    filterHolders.add(_filters[i]); //only retain embedded
+            }
+        }
+        _filters = (FilterHolder[]) LazyList.toArray(filterHolders, FilterHolder.class);
+        _filterMappings = (FilterMapping[]) LazyList.toArray(filterMappings, FilterMapping.class);
+        _matchAfterIndex = (_filterMappings == null || _filterMappings.length == 0 ? -1 : _filterMappings.length-1);
+        _matchBeforeIndex = -1;
+
+
+        // Stop servlets
+        List<ServletHolder> servletHolders = new ArrayList<ServletHolder>();  //will be remaining servlets
+        List<ServletMapping> servletMappings = LazyList.array2List(_servletMappings); //will be remaining mappings
+        if (_servlets!=null)
+        {
+            for (int i=_servlets.length; i-->0;)
+            {
+                try { _servlets[i].stop(); }catch(Exception e){LOG.warn(Log.EXCEPTION,e);}
+                if (_servlets[i].getSource() != Source.EMBEDDED)
+                {
+                    //remove from servlet name map
+                    _servletNameMap.remove(_servlets[i].getName());
+                    //remove any mappings associated with this servlet
+                    ListIterator<ServletMapping> smitor = servletMappings.listIterator();
+                    while (smitor.hasNext())
+                    {
+                        ServletMapping sm = smitor.next();
+                        if (sm.getServletName().equals(_servlets[i].getName()))
+                            smitor.remove();
+                    }
+                }
+                else
+                    servletHolders.add(_servlets[i]); //only retain embedded 
+            }
+        }
+        _servlets = (ServletHolder[]) LazyList.toArray(servletHolders, ServletHolder.class);
+        _servletMappings = (ServletMapping[])LazyList.toArray(servletMappings, ServletMapping.class);
+
+
+        //will be regenerated on next start
+        _filterPathMappings=null;
+        _filterNameMappings=null;       
+        _servletPathMap=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected IdentityService getIdentityService()
+    {
+        return _identityService;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the contextLog.
+     */
+    public Object getContextLog()
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterMappings.
+     */
+    public FilterMapping[] getFilterMappings()
+    {
+        return _filterMappings;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get Filters.
+     * @return Array of defined servlets
+     */
+    public FilterHolder[] getFilters()
+    {
+        return _filters;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** ServletHolder matching path.
+     * @param pathInContext Path within _context.
+     * @return PathMap Entries pathspec to ServletHolder
+     */
+    public PathMap.Entry getHolderEntry(String pathInContext)
+    {
+        if (_servletPathMap==null)
+            return null;
+        return _servletPathMap.getMatch(pathInContext);
+    }
+ 
+    /* ------------------------------------------------------------ */
+    public ServletContext getServletContext()
+    {
+        return _servletContext;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletMappings.
+     */
+    public ServletMapping[] getServletMappings()
+    {
+        return _servletMappings;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletMappings.
+     */
+    public ServletMapping getServletMapping(String pattern)
+    {
+        ServletMapping theMapping = null;
+        if (_servletMappings!=null)
+        {
+            for (ServletMapping m:_servletMappings)
+            {
+                String[] paths=m.getPathSpecs();
+                if (paths!=null)
+                {
+                    for (String path:paths)
+                    {
+                        if (pattern.equals(path))
+                            theMapping = m;
+                    }
+                }
+            }
+        }
+        return theMapping;
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Get Servlets.
+     * @return Array of defined servlets
+     */
+    public ServletHolder[] getServlets()
+    {
+        return _servlets;
+    }
+
+    /* ------------------------------------------------------------ */
+    public ServletHolder getServlet(String name)
+    {
+        return (ServletHolder)_servletNameMap.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        // Get the base requests
+        final String old_servlet_path=baseRequest.getServletPath();
+        final String old_path_info=baseRequest.getPathInfo();
+
+        DispatcherType type = baseRequest.getDispatcherType();
+       
+        ServletHolder servlet_holder=null;
+        UserIdentity.Scope old_scope=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            // Look for the servlet by path
+            PathMap.Entry entry=getHolderEntry(target);
+            if (entry!=null)
+            {
+                servlet_holder=(ServletHolder)entry.getValue();
+
+                String servlet_path_spec=(String)entry.getKey(); 
+                String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
+                String path_info=PathMap.pathInfo(servlet_path_spec,target); 
+                
+                if (DispatcherType.INCLUDE.equals(type))
+                {
+                    baseRequest.setAttribute(Dispatcher.INCLUDE_SERVLET_PATH,servlet_path);
+                    baseRequest.setAttribute(Dispatcher.INCLUDE_PATH_INFO, path_info);
+                }
+                else
+                {
+                    baseRequest.setServletPath(servlet_path);
+                    baseRequest.setPathInfo(path_info);
+                }
+            }      
+        }
+        else
+        {
+            // look for a servlet by name!
+            servlet_holder=(ServletHolder)_servletNameMap.get(target);
+        }
+       
+        if (LOG.isDebugEnabled())
+            LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
+
+        try
+        {
+            // Do the filter/handling thang
+            old_scope=baseRequest.getUserIdentityScope();
+            baseRequest.setUserIdentityScope(servlet_holder);
+
+            // start manual inline of nextScope(target,baseRequest,request,response);
+            if (never())
+                nextScope(target,baseRequest,request,response);
+            else if (_nextScope!=null)
+                _nextScope.doScope(target,baseRequest,request, response);
+            else if (_outerScope!=null)
+                _outerScope.doHandle(target,baseRequest,request, response);
+            else 
+                doHandle(target,baseRequest,request, response);
+            // end manual inline (pathentic attempt to reduce stack depth)
+        }
+        finally
+        {
+            if (old_scope!=null)
+                baseRequest.setUserIdentityScope(old_scope);
+
+            if (!(DispatcherType.INCLUDE.equals(type)))
+            {
+                baseRequest.setServletPath(old_servlet_path);
+                baseRequest.setPathInfo(old_path_info); 
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
+     */
+    @Override
+    public void doHandle(String target, Request baseRequest,HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException
+    {
+        DispatcherType type = baseRequest.getDispatcherType();
+        
+        ServletHolder servlet_holder=(ServletHolder) baseRequest.getUserIdentityScope();
+        FilterChain chain=null;
+
+        // find the servlet
+        if (target.startsWith("/"))
+        {
+            if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
+                chain=getFilterChain(baseRequest, target, servlet_holder);
+        }
+        else
+        {
+            if (servlet_holder!=null)
+            {
+                if (_filterMappings!=null && _filterMappings.length>0)
+                {
+                    chain=getFilterChain(baseRequest, null,servlet_holder);
+                }
+            }
+        }
+
+        LOG.debug("chain={}",chain);
+
+        Throwable th=null;
+        try
+        {
+            if (servlet_holder==null)
+            {
+                if (getHandler()==null)
+                    notFound(request, response);
+                else
+                    nextHandle(target,baseRequest,request,response);
+            }
+            else
+            {
+                // unwrap any tunnelling of base Servlet request/responses
+                ServletRequest req = request;
+                if (req instanceof ServletRequestHttpWrapper)
+                    req = ((ServletRequestHttpWrapper)req).getRequest();
+                ServletResponse res = response;
+                if (res instanceof ServletResponseHttpWrapper)
+                    res = ((ServletResponseHttpWrapper)res).getResponse();
+
+                // Do the filter/handling thang
+                if (chain!=null)
+                    chain.doFilter(req, res);
+                else 
+                    servlet_holder.handle(baseRequest,req,res);
+            }
+        }
+        catch(EofException e)
+        {
+            throw e;
+        }
+        catch(RuntimeIOException e)
+        {
+            throw e;
+        }
+        catch(ContinuationThrowable e)
+        {   
+            throw e;
+        }
+        catch(Exception e)
+        {
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+            {
+                if (e instanceof IOException)
+                    throw (IOException)e;
+                if (e instanceof RuntimeException)
+                    throw (RuntimeException)e;
+                if (e instanceof ServletException)
+                    throw (ServletException)e;
+            }
+
+            // unwrap cause
+            th=e;
+            if (th instanceof UnavailableException)
+            {
+                LOG.debug(th); 
+            }
+            else if (th instanceof ServletException)
+            {
+                LOG.warn(th);
+                Throwable cause=((ServletException)th).getRootCause();
+                if (cause!=null)
+                    th=cause;
+            }
+
+            // handle or log exception
+            if (th instanceof HttpException)
+                throw (HttpException)th;
+            else if (th instanceof RuntimeIOException)
+                throw (RuntimeIOException)th;
+            else if (th instanceof EofException)
+                throw (EofException)th;
+
+            else if (LOG.isDebugEnabled())
+            {
+                LOG.warn(request.getRequestURI(), th); 
+                LOG.debug(request.toString()); 
+            }
+            else if (th instanceof IOException || th instanceof UnavailableException)
+            {
+                LOG.debug(request.getRequestURI(),th);
+            }
+            else
+            {
+                LOG.warn(request.getRequestURI(),th);
+            }
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th);
+            if (!response.isCommitted())
+            {
+                if (th instanceof UnavailableException)
+                {
+                    UnavailableException ue = (UnavailableException)th;
+                    if (ue.isPermanent())
+                        response.sendError(HttpServletResponse.SC_NOT_FOUND);
+                    else
+                        response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                }
+                else
+                    response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+            else
+                LOG.debug("Response already committed for handling "+th);
+            
+        }
+        catch(Error e)
+        {   
+            if (!(DispatcherType.REQUEST.equals(type) || DispatcherType.ASYNC.equals(type)))
+                throw e;
+            th=e;
+            LOG.warn("Error for "+request.getRequestURI(),e);
+            if(LOG.isDebugEnabled())LOG.debug(request.toString());
+
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass());
+            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
+            if (!response.isCommitted())
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            else
+                LOG.debug("Response already committed for handling ",e);
+        }
+        finally
+        {
+            if (servlet_holder!=null)
+                baseRequest.setHandled(true);
+
+            // Complete async requests 
+            if (th!=null && request.isAsyncStarted())
+                ((AsyncContinuation)request.getAsyncContext()).errorComplete();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) 
+    {
+        String key=pathInContext==null?servletHolder.getName():pathInContext;
+        int dispatch = FilterMapping.dispatch(baseRequest.getDispatcherType());
+        
+        if (_filterChainsCached && _chainCache!=null)
+        {
+            FilterChain chain = (FilterChain)_chainCache[dispatch].get(key);
+            if (chain!=null)
+                return chain;
+        }
+        
+        // Build list of filters
+        Object filters= null;
+        // Path filters
+        if (pathInContext!=null && _filterPathMappings!=null)
+        {
+            for (int i= 0; i < _filterPathMappings.size(); i++)
+            {
+                FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
+                if (mapping.appliesTo(pathInContext, dispatch))
+                    filters= LazyList.add(filters, mapping.getFilterHolder());
+            }
+        }
+
+        // Servlet name filters
+        if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
+        {
+            // Servlet name filters
+            if (_filterNameMappings.size() > 0)
+            {
+                Object o= _filterNameMappings.get(servletHolder.getName());
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters=LazyList.add(filters,mapping.getFilterHolder());
+                }
+                
+                o= _filterNameMappings.get("*");
+                for (int i=0; i<LazyList.size(o);i++)
+                {
+                    FilterMapping mapping = (FilterMapping)LazyList.get(o,i);
+                    if (mapping.appliesTo(dispatch))
+                        filters=LazyList.add(filters,mapping.getFilterHolder());
+                }
+            }
+        }
+        
+        if (filters==null)
+            return null;
+        
+        
+        FilterChain chain = null;
+        if (_filterChainsCached)
+        {
+            if (LazyList.size(filters) > 0)
+                chain= new CachedChain(filters, servletHolder);
+
+            final Map<String,FilterChain> cache=_chainCache[dispatch];
+            final Queue<String> lru=_chainLRU[dispatch];
+
+        	// Do we have too many cached chains?
+        	while (_maxFilterChainsCacheSize>0 && cache.size()>=_maxFilterChainsCacheSize)
+        	{
+        	    // The LRU list is not atomic with the cache map, so be prepared to invalidate if 
+        	    // a key is not found to delete.
+        	    // Delete by LRU (where U==created)
+        	    String k=lru.poll();
+        	    if (k==null)
+        	    {
+        	        cache.clear();
+        	        break;
+        	    }
+        	    cache.remove(k);
+        	}
+        	
+        	cache.put(key,chain);
+        	lru.add(key);
+        }
+        else if (LazyList.size(filters) > 0)
+            chain = new Chain(baseRequest,filters, servletHolder);
+    
+        return chain;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void invalidateChainsCache()
+    {
+        if (_chainLRU[FilterMapping.REQUEST]!=null)
+        {
+            _chainLRU[FilterMapping.REQUEST].clear();
+            _chainLRU[FilterMapping.FORWARD].clear();
+            _chainLRU[FilterMapping.INCLUDE].clear();
+            _chainLRU[FilterMapping.ERROR].clear();
+            _chainLRU[FilterMapping.ASYNC].clear();
+
+            _chainCache[FilterMapping.REQUEST].clear();
+            _chainCache[FilterMapping.FORWARD].clear();
+            _chainCache[FilterMapping.INCLUDE].clear();
+            _chainCache[FilterMapping.ERROR].clear();
+            _chainCache[FilterMapping.ASYNC].clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the handler is started and there are no unavailable servlets 
+     */
+    public boolean isAvailable()
+    {
+        if (!isStarted())
+            return false;
+        ServletHolder[] holders = getServlets();
+        for (int i=0;i<holders.length;i++)
+        {
+            ServletHolder holder = holders[i];
+            if (holder!=null && !holder.isAvailable())
+                return false;
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param start True if this handler will start with unavailable servlets
+     */
+    public void setStartWithUnavailable(boolean start)
+    {
+        _startWithUnavailable=start;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if this handler will start with unavailable servlets
+     */
+    public boolean isStartWithUnavailable()
+    {
+        return _startWithUnavailable;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Initialize filters and load-on-startup servlets.
+     * Called automatically from start if autoInitializeServlet is true.
+     */
+    public void initialize()
+        throws Exception
+    {
+        MultiException mx = new MultiException();
+
+        // Start filters
+        if (_filters!=null)
+        {
+            for (int i=0;i<_filters.length; i++)
+                _filters[i].start();
+        }
+        
+        if (_servlets!=null)
+        {
+            // Sort and Initialize servlets
+            ServletHolder[] servlets = (ServletHolder[])_servlets.clone();
+            Arrays.sort(servlets);
+            for (int i=0; i<servlets.length; i++)
+            {
+                try
+                {
+                    if (servlets[i].getClassName()==null && servlets[i].getForcedPath()!=null)
+                    {
+                        ServletHolder forced_holder = (ServletHolder)_servletPathMap.match(servlets[i].getForcedPath());
+                        if (forced_holder==null || forced_holder.getClassName()==null)
+                        {    
+                            mx.add(new IllegalStateException("No forced path servlet for "+servlets[i].getForcedPath()));
+                            continue;
+                        }
+                        servlets[i].setClassName(forced_holder.getClassName());
+                    }
+                    
+                    servlets[i].start();
+                }
+                catch(Throwable e)
+                {
+                    LOG.debug(Log.EXCEPTION,e);
+                    mx.add(e);
+                }
+            } 
+            mx.ifExceptionThrow();  
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the filterChainsCached.
+     */
+    public boolean isFilterChainsCached()
+    {
+        return _filterChainsCached;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * see also newServletHolder(Class)
+     */
+    public ServletHolder newServletHolder(Holder.Source source)
+    {
+        return new ServletHolder(source);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a servlet Holder.
+    public ServletHolder newServletHolder(Class<? extends Servlet> servlet)
+    {
+        return new ServletHolder(servlet);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (String className,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED);
+        holder.setClassName(className);
+        addServletWithMapping(holder,pathSpec);
+        return holder;
+    }   
+    
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @return The servlet holder.
+     */
+    public ServletHolder addServletWithMapping (Class<? extends Servlet> servlet,String pathSpec)
+    {
+        ServletHolder holder = newServletHolder(Holder.Source.EMBEDDED);
+        holder.setHeldClass(servlet);
+        //DUPLICATES adding servlet from addServletWithMapping(holder, pathSpec)?
+        //setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
+        addServletWithMapping(holder,pathSpec);
+        
+        return holder;
+    }   
+    
+    /* ------------------------------------------------------------ */
+    /** conveniance method to add a servlet.
+     * @param servlet servlet holder to add
+     * @param pathSpec servlet mappings for the servletHolder
+     */
+    public void addServletWithMapping (ServletHolder servlet,String pathSpec)
+    {
+        ServletHolder[] holders=getServlets();
+        if (holders!=null)
+            holders = holders.clone();
+        
+        try
+        {
+            setServlets((ServletHolder[])LazyList.addToArray(holders, servlet, ServletHolder.class));
+            
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(servlet.getName());
+            mapping.setPathSpec(pathSpec);
+            setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
+        }
+        catch (Exception e)
+        {
+            setServlets(holders);
+            if (e instanceof RuntimeException)
+                throw (RuntimeException)e;
+            throw new RuntimeException(e);
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */    
+    /**Convenience method to add a pre-constructed ServletHolder.
+     * @param holder
+     */
+    public void addServlet(ServletHolder holder)
+    {
+        setServlets((ServletHolder[])LazyList.addToArray(getServlets(), holder, ServletHolder.class));
+    }
+    
+    /* ------------------------------------------------------------ */    
+    /** Convenience method to add a pre-constructed ServletMapping.
+     * @param mapping
+     */
+    public void addServletMapping (ServletMapping mapping)
+    {
+        setServletMappings((ServletMapping[])LazyList.addToArray(getServletMappings(), mapping, ServletMapping.class));
+    }
+
+    public Set<String>  setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) {
+        if (_contextHandler != null) {
+            return _contextHandler.setServletSecurity(registration, servletSecurityElement);
+        }
+        return Collections.emptySet();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see #newFilterHolder(Class)
+     */
+    public FilterHolder newFilterHolder(Holder.Source source)
+    {
+        return new FilterHolder(source);
+    }
+
+    /* ------------------------------------------------------------ */
+    public FilterHolder getFilter(String name)
+    {
+        return (FilterHolder)_filterNameMap.get(name);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        
+        return holder;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        holder.setClassName(className);
+        
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = (FilterHolder[])holders.clone();
+        
+        try
+        {
+            setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
+            
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatcherTypes(dispatches);
+            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
+            addFilterMapping(mapping);
+            
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+            
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param filter  class of filter to create
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (Class<? extends Filter> filter,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        holder.setHeldClass(filter);
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        
+        return holder;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param className of filter
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     * @return The filter holder.
+     */
+    public FilterHolder addFilterWithMapping (String className,String pathSpec,int dispatches)
+    {
+        FilterHolder holder = newFilterHolder(Holder.Source.EMBEDDED);
+        holder.setClassName(className);
+        
+        addFilterWithMapping(holder,pathSpec,dispatches);
+        return holder;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter.
+     * @param holder filter holder to add
+     * @param pathSpec filter mappings for filter
+     * @param dispatches see {@link FilterMapping#setDispatches(int)}
+     */
+    public void addFilterWithMapping (FilterHolder holder,String pathSpec,int dispatches)
+    {
+        FilterHolder[] holders = getFilters();
+        if (holders!=null)
+            holders = (FilterHolder[])holders.clone();
+        
+        try
+        {
+            setFilters((FilterHolder[])LazyList.addToArray(holders, holder, FilterHolder.class));
+            
+            FilterMapping mapping = new FilterMapping();
+            mapping.setFilterName(holder.getName());
+            mapping.setPathSpec(pathSpec);
+            mapping.setDispatches(dispatches);
+            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), mapping, FilterMapping.class));
+            addFilterMapping(mapping);
+        }
+        catch (RuntimeException e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+        catch (Error e)
+        {
+            setFilters(holders);
+            throw e;
+        }
+            
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a filter with a mapping
+     * @param className
+     * @param pathSpec
+     * @param dispatches
+     * @return the filter holder created
+     * @deprecated use {@link #addFilterWithMapping(Class, String, EnumSet<DispatcherType>)} instead
+     */
+    public FilterHolder addFilter (String className,String pathSpec,EnumSet<DispatcherType> dispatches)
+    {
+        return addFilterWithMapping(className, pathSpec, dispatches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * convenience method to add a filter and mapping
+     * @param filter
+     * @param filterMapping
+     */
+    public void addFilter (FilterHolder filter, FilterMapping filterMapping)
+    {
+        if (filter != null)
+            setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
+        if (filterMapping != null)
+            //setFilterMappings((FilterMapping[])LazyList.addToArray(getFilterMappings(), filterMapping, FilterMapping.class));
+            addFilterMapping(filterMapping);
+    }
+    
+    /* ------------------------------------------------------------ */  
+    /** Convenience method to add a preconstructed FilterHolder
+     * @param filter
+     */
+    public void addFilter (FilterHolder filter)
+    {
+        if (filter != null)
+            setFilters((FilterHolder[])LazyList.addToArray(getFilters(), filter, FilterHolder.class));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void addFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        { 
+            Source source = (mapping.getFilterHolder()==null?null:mapping.getFilterHolder().getSource());
+            FilterMapping[] mappings =getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping,0,false));
+                if (source != null && source == Source.JAVAX_API)
+                    _matchAfterIndex = 0;
+            }
+            else
+            {
+                //there are existing entries. If this is a programmatic filtermapping, it is added at the end of the list.
+                //If this is a normal filtermapping, it is inserted after all the other filtermappings (matchBefores and normals), 
+                //but before the first matchAfter filtermapping.
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    if (_matchAfterIndex < 0)
+                        _matchAfterIndex = getFilterMappings().length-1;
+                }
+                else
+                {
+                    //insert non-programmatic filter mappings before any matchAfters, if any
+                    if (_matchAfterIndex < 0)
+                        setFilterMappings(insertFilterMapping(mapping,mappings.length-1, false));
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, _matchAfterIndex, true);
+                        ++_matchAfterIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Convenience method to add a preconstructed FilterMapping
+     * @param mapping
+     */
+    public void prependFilterMapping (FilterMapping mapping)
+    {
+        if (mapping != null)
+        {
+            Source source = mapping.getFilterHolder().getSource();
+            
+            FilterMapping[] mappings = getFilterMappings();
+            if (mappings==null || mappings.length==0)
+            {
+                setFilterMappings(insertFilterMapping(mapping, 0, false));
+                if (source != null && Source.JAVAX_API == source)
+                    _matchBeforeIndex = 0;
+            }
+            else
+            {
+                if (source != null && Source.JAVAX_API == source)
+                {
+                    //programmatically defined filter mappings are prepended to mapping list in the order
+                    //in which they were defined. In other words, insert this mapping at the tail of the 
+                    //programmatically prepended filter mappings, BEFORE the first web.xml defined filter mapping.
+
+                    if (_matchBeforeIndex < 0)
+                    { 
+                        //no programmatically defined prepended filter mappings yet, prepend this one
+                        _matchBeforeIndex = 0;
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                        setFilterMappings(new_mappings);
+                    }
+                    else
+                    {
+                        FilterMapping[] new_mappings = insertFilterMapping(mapping,_matchBeforeIndex, false);
+                        ++_matchBeforeIndex;
+                        setFilterMappings(new_mappings);
+                    }
+                }
+                else
+                {
+                    //non programmatically defined, just prepend to list
+                    FilterMapping[] new_mappings = insertFilterMapping(mapping, 0, true);
+                    setFilterMappings(new_mappings);
+                }
+                
+                //adjust matchAfterIndex ptr to take account of the mapping we just prepended
+                if (_matchAfterIndex >= 0)
+                    ++_matchAfterIndex;
+            }
+        }
+    }
+    
+    
+    
+    /**
+     * Insert a filtermapping in the list
+     * @param mapping the FilterMapping to add
+     * @param pos the position in the existing arry at which to add it
+     * @param before if true, insert before  pos, if false insert after it
+     * @return
+     */
+    protected FilterMapping[] insertFilterMapping (FilterMapping mapping, int pos, boolean before)
+    {
+        if (pos < 0)
+            throw new IllegalArgumentException("FilterMapping insertion pos < 0");
+        FilterMapping[] mappings = getFilterMappings();
+        
+        if (mappings==null || mappings.length==0)
+        {
+            return new FilterMapping[] {mapping};
+        }
+        FilterMapping[] new_mappings = new FilterMapping[mappings.length+1];
+
+    
+        if (before)
+        {
+            //copy existing filter mappings up to but not including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos);
+
+            //add in the new mapping
+            new_mappings[pos] = mapping; 
+
+            //copy the old pos mapping and any remaining existing mappings
+            System.arraycopy(mappings,pos,new_mappings,pos+1, mappings.length-pos);
+
+        }
+        else
+        {
+            //copy existing filter mappings up to and including the pos
+            System.arraycopy(mappings,0,new_mappings,0,pos+1);
+            //add in the new mapping after the pos
+            new_mappings[pos+1] = mapping;   
+
+            //copy the remaining existing mappings
+            if (mappings.length > pos+1)
+                System.arraycopy(mappings,pos+1,new_mappings,pos+2, mappings.length-(pos+1));
+        }
+        return new_mappings;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateNameMappings()
+    {   
+        // update filter name map
+        _filterNameMap.clear();
+        if (_filters!=null)
+        {   
+            for (int i=0;i<_filters.length;i++)
+            {
+                _filterNameMap.put(_filters[i].getName(),_filters[i]);
+                _filters[i].setServletHandler(this);
+            }
+        }
+
+        // Map servlet names to holders
+        _servletNameMap.clear();
+        if (_servlets!=null)
+        {   
+            // update the maps
+            for (int i=0;i<_servlets.length;i++)
+            {
+                _servletNameMap.put(_servlets[i].getName(),_servlets[i]);
+                _servlets[i].setServletHandler(this);
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected synchronized void updateMappings()
+    {   
+        // update filter mappings
+        if (_filterMappings==null)
+        {
+            _filterPathMappings=null;
+            _filterNameMappings=null;
+        }
+        else 
+        {
+            _filterPathMappings=new ArrayList();
+            _filterNameMappings=new MultiMap();
+            for (int i=0;i<_filterMappings.length;i++)
+            {
+                FilterHolder filter_holder = (FilterHolder)_filterNameMap.get(_filterMappings[i].getFilterName());
+                if (filter_holder==null)
+                    throw new IllegalStateException("No filter named "+_filterMappings[i].getFilterName());
+                _filterMappings[i].setFilterHolder(filter_holder);    
+                if (_filterMappings[i].getPathSpecs()!=null)
+                    _filterPathMappings.add(_filterMappings[i]);
+                
+                if (_filterMappings[i].getServletNames()!=null)
+                {
+                    String[] names=_filterMappings[i].getServletNames();
+                    for (int j=0;j<names.length;j++)
+                    {
+                        if (names[j]!=null)
+                            _filterNameMappings.add(names[j], _filterMappings[i]);  
+                    }
+                }
+            }
+        }
+
+        // Map servlet paths to holders
+        if (_servletMappings==null || _servletNameMap==null)
+        {
+            _servletPathMap=null;
+        }
+        else
+        {
+            PathMap pm = new PathMap();
+            
+            // update the maps
+            for (int i=0;i<_servletMappings.length;i++)
+            {
+                ServletHolder servlet_holder = (ServletHolder)_servletNameMap.get(_servletMappings[i].getServletName());
+                if (servlet_holder==null)
+                    throw new IllegalStateException("No such servlet: "+_servletMappings[i].getServletName());
+                else if (servlet_holder.isEnabled() && _servletMappings[i].getPathSpecs()!=null)
+                {
+                    String[] pathSpecs = _servletMappings[i].getPathSpecs();
+                    for (int j=0;j<pathSpecs.length;j++)
+                        if (pathSpecs[j]!=null)
+                            pm.put(pathSpecs[j],servlet_holder);
+                }
+            }
+            
+            _servletPathMap=pm;
+        }
+        
+        // flush filter chain cache
+        if (_chainCache!=null)
+        {
+            for (int i=_chainCache.length;i-->0;)
+            {
+                if (_chainCache[i]!=null)
+                    _chainCache[i].clear();
+            }
+        }
+
+        if (LOG.isDebugEnabled()) 
+        {
+            LOG.debug("filterNameMap="+_filterNameMap);
+            LOG.debug("pathFilters="+_filterPathMappings);
+            LOG.debug("servletFilterMap="+_filterNameMappings);
+            LOG.debug("servletPathMap="+_servletPathMap);
+            LOG.debug("servletNameMap="+_servletNameMap);
+        }
+        
+        try
+        {
+            if (_contextHandler!=null && _contextHandler.isStarted() || _contextHandler==null && isStarted())
+                initialize();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void notFound(HttpServletRequest request,
+                  HttpServletResponse response)
+        throws IOException
+    {
+        if(LOG.isDebugEnabled())
+            LOG.debug("Not Found "+request.getRequestURI());
+        //Override to send an error back, eg with: response.sendError(HttpServletResponse.SC_NOT_FOUND);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterChainsCached The filterChainsCached to set.
+     */
+    public void setFilterChainsCached(boolean filterChainsCached)
+    {
+        _filterChainsCached = filterChainsCached;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filterMappings The filterMappings to set.
+     */
+    public void setFilterMappings(FilterMapping[] filterMappings)
+    {
+        if (getServer()!=null)
+            getServer().getContainer().update(this,_filterMappings,filterMappings,"filterMapping",true);
+        _filterMappings = filterMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized void setFilters(FilterHolder[] holders)
+    {
+        if (getServer()!=null)
+            getServer().getContainer().update(this,_filters,holders,"filter",true);
+        _filters=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletMappings The servletMappings to set.
+     */
+    public void setServletMappings(ServletMapping[] servletMappings)
+    {
+        if (getServer()!=null)
+            getServer().getContainer().update(this,_servletMappings,servletMappings,"servletMapping",true);
+        _servletMappings = servletMappings;
+        updateMappings();
+        invalidateChainsCache();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set Servlets.
+     * @param holders Array of servletsto define
+     */
+    public synchronized void setServlets(ServletHolder[] holders)
+    {
+        if (getServer()!=null)
+            getServer().getContainer().update(this,_servlets,holders,"servlet",true);
+        _servlets=holders;
+        updateNameMappings();
+        invalidateChainsCache();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class CachedChain implements FilterChain
+    {
+        FilterHolder _filterHolder;
+        CachedChain _next;
+        ServletHolder _servletHolder;
+
+        /* ------------------------------------------------------------ */
+        CachedChain(Object filters, ServletHolder servletHolder)
+        {
+            if (LazyList.size(filters)>0)
+            {
+                _filterHolder=(FilterHolder)LazyList.get(filters, 0);
+                filters=LazyList.remove(filters,0);
+                _next=new CachedChain(filters,servletHolder);
+            }
+            else
+                _servletHolder=servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void doFilter(ServletRequest request, ServletResponse response) 
+            throws IOException, ServletException
+        {                   
+            final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+
+            // pass to next filter
+            if (_filterHolder!=null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call filter " + _filterHolder);
+                Filter filter= _filterHolder.getFilter();
+                if (_filterHolder.isAsyncSupported())
+                    filter.doFilter(request, response, _next);
+                else
+                {
+                    final boolean suspendable=baseRequest.isAsyncSupported();
+                    if (suspendable)
+                    {
+                        try
+                        {
+                            baseRequest.setAsyncSupported(false);
+                            filter.doFilter(request, response, _next);
+                        }
+                        finally
+                        {
+                            baseRequest.setAsyncSupported(true);
+                        }
+                    }
+                    else
+                        filter.doFilter(request, response, _next);
+                }
+                return;
+            }
+
+            // Call servlet
+            
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder != null)
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("call servlet " + _servletHolder);
+                _servletHolder.handle(baseRequest,request, response);
+            }
+            else if (getHandler()==null)
+                notFound(srequest, (HttpServletResponse)response);
+            else
+                nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
+                           baseRequest,srequest,(HttpServletResponse)response);
+            
+        }
+        
+        public String toString()
+        {
+            if (_filterHolder!=null)
+                return _filterHolder+"->"+_next.toString();
+            if (_servletHolder!=null)
+                return _servletHolder.toString();
+            return "null";
+        }
+    }  
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class Chain implements FilterChain
+    {
+        final Request _baseRequest;
+        final Object _chain;
+        final ServletHolder _servletHolder;
+        int _filter= 0;
+
+        /* ------------------------------------------------------------ */
+        Chain(Request baseRequest, Object filters, ServletHolder servletHolder)
+        {
+            _baseRequest=baseRequest;
+            _chain= filters;
+            _servletHolder= servletHolder;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException
+        {
+            if (LOG.isDebugEnabled()) 
+                LOG.debug("doFilter " + _filter);
+
+            // pass to next filter
+            if (_filter < LazyList.size(_chain))
+            {
+                FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("call filter " + holder);
+                Filter filter= holder.getFilter();
+                
+                if (holder.isAsyncSupported() || !_baseRequest.isAsyncSupported())
+                {
+                    filter.doFilter(request, response, this);
+                }
+                else
+                {
+                    try
+                    {
+                        _baseRequest.setAsyncSupported(false);
+                        filter.doFilter(request, response, this);
+                    }
+                    finally
+                    {
+                        _baseRequest.setAsyncSupported(true);
+                    }
+                }
+                    
+                return;
+            }
+
+            // Call servlet
+            HttpServletRequest srequest = (HttpServletRequest)request;
+            if (_servletHolder != null)
+            {
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("call servlet " + _servletHolder);
+                _servletHolder.handle(_baseRequest,request, response);
+            }
+            else if (getHandler()==null)
+                notFound(srequest, (HttpServletResponse)response);
+            else
+            {            
+                Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
+                nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()),
+                           baseRequest,srequest,(HttpServletResponse)response);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public String toString()
+        {
+            StringBuilder b = new StringBuilder();
+            for (int i=0; i<LazyList.size(_chain);i++)
+            {
+                Object o=LazyList.get(_chain, i);
+                b.append(o.toString());
+                b.append("->");
+            }
+            b.append(_servletHolder);
+            return b.toString();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The maximum entries in a filter chain cache.
+     */
+    public int getMaxFilterChainsCacheSize()
+    {
+        return _maxFilterChainsCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum filter chain cache size.
+     * Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
+     * is greater than zero, then the cache is flushed whenever it grows to be this size.
+     * 
+     * @param maxFilterChainsCacheSize  the maximum number of entries in a filter chain cache.
+     */
+    public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
+    {
+        _maxFilterChainsCacheSize = maxFilterChainsCacheSize;
+    }
+    
+    /* ------------------------------------------------------------ */
+    void destroyServlet(Servlet servlet)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyServlet(servlet);
+    }
+
+    /* ------------------------------------------------------------ */
+    void destroyFilter(Filter filter)
+    {
+        if (_contextHandler!=null)
+            _contextHandler.destroyFilter(filter);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        super.dumpThis(out);
+        dump(out,indent,
+                TypeUtil.asList(getHandlers()),
+                getBeans(),
+                TypeUtil.asList(getFilterMappings()),
+                TypeUtil.asList(getFilters()),
+                TypeUtil.asList(getServletMappings()),
+                TypeUtil.asList(getServlets()));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/ServletHolder.java b/src/java/org/eclipse/jetty/servlet/ServletHolder.java
new file mode 100644
index 0000000..30542b1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/ServletHolder.java
@@ -0,0 +1,969 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.SingleThreadModel;
+import javax.servlet.UnavailableException;
+
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.RunAsToken;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+
+/* --------------------------------------------------------------------- */
+/** Servlet Instance and Context Holder.
+ * Holds the name, params and some state of a javax.servlet.Servlet
+ * instance. It implements the ServletConfig interface.
+ * This class will organise the loading of the servlet when needed or
+ * requested.
+ *
+ * 
+ */
+public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope, Comparable
+{
+    private static final Logger LOG = Log.getLogger(ServletHolder.class);
+
+    /* ---------------------------------------------------------------- */
+    private int _initOrder;
+    private boolean _initOnStartup=false;
+    private Map<String, String> _roleMap;
+    private String _forcedPath;
+    private String _runAsRole;
+    private RunAsToken _runAsToken;
+    private IdentityService _identityService;
+    private ServletRegistration.Dynamic _registration;
+    
+    
+    private transient Servlet _servlet;
+    private transient Config _config;
+    private transient long _unavailable;
+    private transient boolean _enabled = true;
+    private transient UnavailableException _unavailableEx;
+    public static final Map<String,String> NO_MAPPED_ROLES = Collections.emptyMap();
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder()
+    {
+        this(Source.EMBEDDED);
+    }
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor .
+     */
+    public ServletHolder(Holder.Source creator)
+    {
+        super(creator);
+    }
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor for existing servlet.
+     */
+    public ServletHolder(Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setServlet(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setHeldClass(servlet);
+    }
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(String name, Servlet servlet)
+    {
+        this(Source.EMBEDDED);
+        setName(name);
+        setServlet(servlet);
+    }
+    
+    /* ---------------------------------------------------------------- */
+    /** Constructor for servlet class.
+     */
+    public ServletHolder(Class<? extends Servlet> servlet)
+    {
+        this(Source.EMBEDDED);
+        setHeldClass(servlet);
+    }
+
+    /* ---------------------------------------------------------------- */
+    /**
+     * @return The unavailable exception or null if not unavailable
+     */
+    public UnavailableException getUnavailableException()
+    {
+        return _unavailableEx;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public synchronized void setServlet(Servlet servlet)
+    {
+        if (servlet==null || servlet instanceof SingleThreadModel)
+            throw new IllegalArgumentException();
+
+        _extInstance=true;
+        _servlet=servlet;
+        setHeldClass(servlet.getClass());
+        if (getName()==null)
+            setName(servlet.getClass().getName()+"-"+super.hashCode());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getInitOrder()
+    {
+        return _initOrder;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the initialize order.
+     * Holders with order<0, are initialized on use. Those with
+     * order>=0 are initialized in increasing order when the handler
+     * is started.
+     */
+    public void setInitOrder(int order)
+    {
+        _initOnStartup=true;
+        _initOrder = order;
+    }
+    
+    public boolean isSetInitOrder()
+    {
+        return _initOnStartup;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Comparitor by init order.
+     */
+    public int compareTo(Object o)
+    {
+        if (o instanceof ServletHolder)
+        {
+            ServletHolder sh= (ServletHolder)o;
+            if (sh==this)
+                return 0;
+            if (sh._initOrder<_initOrder)
+                return 1;
+            if (sh._initOrder>_initOrder)
+                return -1;
+            
+            int c=(_className!=null && sh._className!=null)?_className.compareTo(sh._className):0;
+            if (c==0)
+                c=_name.compareTo(sh._name);
+            return c;
+        }
+        return 1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean equals(Object o)
+    {
+        return compareTo(o)==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int hashCode()
+    {
+        return _name==null?System.identityHashCode(this):_name.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Link a user role.
+     * Translate the role name used by a servlet, to the link name
+     * used by the container.
+     * @param name The role name as used by the servlet
+     * @param link The role name as used by the container.
+     */
+    public synchronized void setUserRoleLink(String name,String link)
+    {
+        if (_roleMap==null)
+            _roleMap=new HashMap<String, String>();
+        _roleMap.put(name,link);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** get a user role link.
+     * @param name The name of the role
+     * @return The name as translated by the link. If no link exists,
+     * the name is returned.
+     */
+    public String getUserRoleLink(String name)
+    {
+        if (_roleMap==null)
+            return name;
+        String link= _roleMap.get(name);
+        return (link==null)?name:link;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Map<String, String> getRoleMap()
+    {
+        return _roleMap == null? NO_MAPPED_ROLES : _roleMap;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the forcedPath.
+     */
+    public String getForcedPath()
+    {
+        return _forcedPath;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param forcedPath The forcedPath to set.
+     */
+    public void setForcedPath(String forcedPath)
+    {
+        _forcedPath = forcedPath;
+    }
+    
+    public boolean isEnabled()
+    {
+        return _enabled;
+    }
+
+
+    public void setEnabled(boolean enabled)
+    {
+        _enabled = enabled;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void doStart()
+        throws Exception
+    {
+        _unavailable=0;
+        if (!_enabled)
+            return;
+        
+        
+        //check servlet has a class (ie is not a preliminary registration). If preliminary, fail startup.
+        try
+        {
+            super.doStart();
+        } 
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+
+
+        //servlet is not an instance of javax.servlet.Servlet
+        try
+        {
+            checkServletType();
+        }
+        catch (UnavailableException ue)
+        {
+            makeUnavailable(ue);
+            if (_servletHandler.isStartWithUnavailable())
+            {
+                LOG.ignore(ue);
+                return;
+            }
+            else
+                throw ue;
+        }
+        
+
+        _identityService = _servletHandler.getIdentityService();
+        if (_identityService!=null && _runAsRole!=null)
+            _runAsToken=_identityService.newRunAsToken(_runAsRole);
+        
+        _config=new Config();
+
+        if (_class!=null && javax.servlet.SingleThreadModel.class.isAssignableFrom(_class))
+            _servlet = new SingleThreadedWrapper();
+
+        if (_extInstance || _initOnStartup)
+        {
+            try
+            {
+                initServlet();
+            }
+            catch(Exception e)
+            {
+                if (_servletHandler.isStartWithUnavailable())
+                    LOG.ignore(e);
+                else
+                    throw e;
+            }
+        }  
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doStop()
+        throws Exception
+    {
+        Object old_run_as = null;
+        if (_servlet!=null)
+        {       
+            try
+            {
+                if (_identityService!=null)
+                    old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+
+                destroyInstance(_servlet);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (_identityService!=null)
+                    _identityService.unsetRunAs(old_run_as);
+            }
+        }
+
+        if (!_extInstance)
+            _servlet=null;
+
+        _config=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void destroyInstance (Object o)
+    throws Exception
+    {
+        if (o==null)
+            return;
+        Servlet servlet =  ((Servlet)o);
+        getServletHandler().destroyServlet(servlet);
+        servlet.destroy();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet.
+     * @return The servlet
+     */
+    public synchronized Servlet getServlet()
+        throws ServletException
+    {
+        // Handle previous unavailability
+        if (_unavailable!=0)
+        {
+            if (_unavailable<0 || _unavailable>0 && System.currentTimeMillis()<_unavailable)
+                throw _unavailableEx;
+            _unavailable=0;
+            _unavailableEx=null;
+        }
+
+        if (_servlet==null)
+            initServlet();
+        return _servlet;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the servlet instance (no initialization done).
+     * @return The servlet or null
+     */
+    public Servlet getServletInstance()
+    {
+        return _servlet;
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * Check to ensure class of servlet is acceptable.
+     * @throws UnavailableException
+     */
+    public void checkServletType ()
+        throws UnavailableException
+    {
+        if (_class==null || !javax.servlet.Servlet.class.isAssignableFrom(_class))
+        {
+            throw new UnavailableException("Servlet "+_class+" is not a javax.servlet.Servlet");
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if the holder is started and is not unavailable
+     */
+    public boolean isAvailable()
+    {
+        if (isStarted()&& _unavailable==0)
+            return true;
+        try 
+        {
+            getServlet();
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+
+        return isStarted()&& _unavailable==0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    private void makeUnavailable(UnavailableException e)
+    {
+        if (_unavailableEx==e && _unavailable!=0)
+            return;
+
+        _servletHandler.getServletContext().log("unavailable",e);
+
+        _unavailableEx=e;
+        _unavailable=-1;
+        if (e.isPermanent())   
+            _unavailable=-1;
+        else
+        {
+            if (_unavailableEx.getUnavailableSeconds()>0)
+                _unavailable=System.currentTimeMillis()+1000*_unavailableEx.getUnavailableSeconds();
+            else
+                _unavailable=System.currentTimeMillis()+5000; // TODO configure
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+
+    private void makeUnavailable(final Throwable e)
+    {
+        if (e instanceof UnavailableException)
+            makeUnavailable((UnavailableException)e);
+        else
+        {
+            ServletContext ctx = _servletHandler.getServletContext();
+            if (ctx==null)
+                LOG.info("unavailable",e);
+            else
+                ctx.log("unavailable",e);
+            _unavailableEx=new UnavailableException(String.valueOf(e),-1)
+            {
+                {
+                    initCause(e);
+                }
+            };
+            _unavailable=-1;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void initServlet()
+    	throws ServletException
+    {
+        Object old_run_as = null;
+        try
+        {
+            if (_servlet==null)
+                _servlet=newInstance();
+            if (_config==null)
+                _config=new Config();
+            
+            // Handle run as
+            if (_identityService!=null)
+            {
+                old_run_as=_identityService.setRunAs(_identityService.getSystemUserIdentity(),_runAsToken);
+            }
+            
+            // Handle configuring servlets that implement org.apache.jasper.servlet.JspServlet
+            if (isJspServlet())
+            {
+                initJspServlet();
+            }
+
+            initMultiPart();
+            
+            _servlet.init(_config);
+        }
+        catch (UnavailableException e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (ServletException e)
+        {
+            makeUnavailable(e.getCause()==null?e:e.getCause());
+            _servlet=null;
+            _config=null;
+            throw e;
+        }
+        catch (Exception e)
+        {
+            makeUnavailable(e);
+            _servlet=null;
+            _config=null;
+            throw new ServletException(this.toString(),e);
+        }
+        finally
+        {
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws Exception
+     */
+    protected void initJspServlet () throws Exception
+    {
+        ContextHandler ch = ((ContextHandler.Context)getServletHandler().getServletContext()).getContextHandler();
+        
+        /* Set the webapp's classpath for Jasper */
+        ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath());
+
+        /* Set the system classpath for Jasper */
+        setInitParameter("com.sun.appserv.jsp.classpath", Loader.getClassPath(ch.getClassLoader().getParent())); 
+        
+        /* Set up other classpath attribute */
+        if ("?".equals(getInitParameter("classpath")))
+        {
+            String classpath = ch.getClassPath();
+            LOG.debug("classpath=" + classpath);
+            if (classpath != null) 
+                setInitParameter("classpath", classpath);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Register a ServletRequestListener that will ensure tmp multipart
+     * files are deleted when the request goes out of scope.
+     * 
+     * @throws Exception
+     */
+    protected void initMultiPart () throws Exception
+    {
+        //if this servlet can handle multipart requests, ensure tmp files will be
+        //cleaned up correctly
+        if (((Registration)getRegistration()).getMultipartConfig() != null)
+        {
+            //Register a listener to delete tmp files that are created as a result of this
+            //servlet calling Request.getPart() or Request.getParts()
+            ContextHandler ch = ((ContextHandler.Context)getServletHandler().getServletContext()).getContextHandler();
+            ch.addEventListener(new Request.MultiPartCleanerListener());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getContextPath()
+     */
+    public String getContextPath()
+    {
+        return _config.getServletContext().getContextPath();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.server.UserIdentity.Scope#getRoleRefMap()
+     */
+    public Map<String, String> getRoleRefMap()
+    {
+        return _roleMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getRunAsRole() 
+    {
+        return _runAsRole;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setRunAsRole(String role) 
+    {
+        _runAsRole = role;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Service a request with this servlet.
+     */
+    public void handle(Request baseRequest,
+                       ServletRequest request,
+                       ServletResponse response)
+        throws ServletException,
+               UnavailableException,
+               IOException
+    {
+        if (_class==null)
+            throw new UnavailableException("Servlet Not Initialized");
+        
+        Servlet servlet=_servlet;
+        synchronized(this)
+        {
+            if (!isStarted())
+                throw new UnavailableException("Servlet not initialized", -1);
+            if (_unavailable!=0 || !_initOnStartup)
+                servlet=getServlet();
+            if (servlet==null)
+                throw new UnavailableException("Could not instantiate "+_class);
+        }
+        
+        // Service the request
+        boolean servlet_error=true;
+        Object old_run_as = null;
+        boolean suspendable = baseRequest.isAsyncSupported();
+        try
+        {
+            // Handle aliased path
+            if (_forcedPath!=null)
+                // TODO complain about poor naming to the Jasper folks
+                request.setAttribute("org.apache.catalina.jsp_file",_forcedPath);
+
+            // Handle run as
+            if (_identityService!=null)
+                old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
+
+            if (!isAsyncSupported())
+                baseRequest.setAsyncSupported(false);
+
+            MultipartConfigElement mpce = ((Registration)getRegistration()).getMultipartConfig();
+            if (mpce != null)
+                request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
+
+            servlet.service(request,response);
+            servlet_error=false;
+        }
+        catch(UnavailableException e)
+        {
+            makeUnavailable(e);
+            throw _unavailableEx;
+        }
+        finally
+        {
+            baseRequest.setAsyncSupported(suspendable);
+            
+            // pop run-as role
+            if (_identityService!=null)
+                _identityService.unsetRunAs(old_run_as);
+
+            // Handle error params.
+            if (servlet_error)
+                request.setAttribute("javax.servlet.error.servlet_name",getName());
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet ()
+    {
+        if (_servlet == null)
+            return false;
+        
+        Class c = _servlet.getClass();
+        
+        boolean result = false;
+        while (c != null && !result)
+        {
+            result = isJspServlet(c.getName());
+            c = c.getSuperclass();
+        }
+        
+        return result;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private boolean isJspServlet (String classname)
+    {
+        if (classname == null)
+            return false;
+        return ("org.apache.jasper.servlet.JspServlet".equals(classname));
+    }
+
+ 
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    protected class Config extends HolderConfig implements ServletConfig
+    {   
+        /* -------------------------------------------------------- */
+        public String getServletName()
+        {
+            return getName();
+        }
+        
+    }
+
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    public class Registration extends HolderRegistration implements ServletRegistration.Dynamic
+    {
+        protected MultipartConfigElement _multipartConfig;       
+        
+        public Set<String> addMapping(String... urlPatterns)
+        {
+            illegalStateIfContextStarted();
+            Set<String> clash=null;
+            for (String pattern : urlPatterns)
+            {
+                ServletMapping mapping = _servletHandler.getServletMapping(pattern);
+                if (mapping!=null)
+                {
+                    //if the servlet mapping was from a default descriptor, then allow it to be overridden
+                    if (!mapping.isDefault())
+                    {
+                        if (clash==null)
+                            clash=new HashSet<String>();
+                        clash.add(pattern);
+                    }
+                }
+            }
+            
+            //if there were any clashes amongst the urls, return them
+            if (clash!=null)
+                return clash;
+            
+            //otherwise apply all of them
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(ServletHolder.this.getName());
+            mapping.setPathSpecs(urlPatterns);
+            _servletHandler.addServletMapping(mapping);
+            
+            return Collections.emptySet();
+        }
+
+        public Collection<String> getMappings()
+        {
+            ServletMapping[] mappings =_servletHandler.getServletMappings();
+            List<String> patterns=new ArrayList<String>();
+            if (mappings!=null)
+            {
+                for (ServletMapping mapping : mappings)
+                {
+                    if (!mapping.getServletName().equals(getName()))
+                        continue;
+                    String[] specs=mapping.getPathSpecs();
+                    if (specs!=null && specs.length>0)
+                        patterns.addAll(Arrays.asList(specs));
+                }
+            }
+            return patterns;
+        }
+
+        @Override
+        public String getRunAsRole() 
+        {
+            return _runAsRole;
+        }
+
+        @Override
+        public void setLoadOnStartup(int loadOnStartup)
+        {
+            illegalStateIfContextStarted();
+            ServletHolder.this.setInitOrder(loadOnStartup);
+        }
+        
+        public int getInitOrder()
+        {
+            return ServletHolder.this.getInitOrder();
+        }
+
+        @Override
+        public void setMultipartConfig(MultipartConfigElement element) 
+        {
+            _multipartConfig = element;
+        }
+        
+        public MultipartConfigElement getMultipartConfig()
+        {
+            return _multipartConfig;
+        }
+
+        @Override
+        public void setRunAsRole(String role) 
+        {
+            _runAsRole = role;
+        }
+
+        @Override
+        public Set<String> setServletSecurity(ServletSecurityElement securityElement) 
+        {
+            return _servletHandler.setServletSecurity(this, securityElement);
+        }
+    }
+    
+    public ServletRegistration.Dynamic getRegistration()
+    {
+        if (_registration == null)
+            _registration = new Registration();
+        return _registration;
+    }
+    
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    /* -------------------------------------------------------- */
+    private class SingleThreadedWrapper implements Servlet
+    {
+        Stack<Servlet> _stack=new Stack<Servlet>();
+        
+        public void destroy()
+        {
+            synchronized(this)
+            {
+                while(_stack.size()>0)
+                    try { (_stack.pop()).destroy(); } catch (Exception e) { LOG.warn(e); }
+            }
+        }
+
+        public ServletConfig getServletConfig()
+        {
+            return _config;
+        }
+
+        public String getServletInfo()
+        {
+            return null;
+        }
+
+        public void init(ServletConfig config) throws ServletException
+        {
+            synchronized(this)
+            {
+                if(_stack.size()==0)
+                {
+                    try
+                    {
+                        Servlet s = newInstance();
+                        s.init(config);
+                        _stack.push(s);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+        }
+
+        public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+        {
+            Servlet s;
+            synchronized(this)
+            {
+                if(_stack.size()>0)
+                    s=(Servlet)_stack.pop();
+                else
+                {
+                    try
+                    {
+                        s = newInstance();
+                        s.init(_config);
+                    }
+                    catch (ServletException e)
+                    {
+                        throw e;
+                    }
+                    catch (Exception e)
+                    {
+                        throw new ServletException(e);
+                    }
+                }
+            }
+            
+            try
+            {
+                s.service(req,res);
+            }
+            finally
+            {
+                synchronized(this)
+                {
+                    _stack.push(s);
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the newly created Servlet instance
+     * @throws ServletException
+     * @throws IllegalAccessException
+     * @throws InstantiationException
+     */
+    protected Servlet newInstance() throws ServletException, IllegalAccessException, InstantiationException
+    {
+        try
+        {
+            ServletContext ctx = getServletHandler().getServletContext();
+            if (ctx==null)
+                return getHeldClass().newInstance();
+            return ((ServletContextHandler.Context)ctx).createServlet(getHeldClass());
+        }
+        catch (ServletException se)
+        {
+            Throwable cause = se.getRootCause();
+            if (cause instanceof InstantiationException)
+                throw (InstantiationException)cause;
+            if (cause instanceof IllegalAccessException)
+                throw (IllegalAccessException)cause;
+            throw se;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/ServletMapping.java b/src/java/org/eclipse/jetty/servlet/ServletMapping.java
new file mode 100644
index 0000000..25c0ae0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/ServletMapping.java
@@ -0,0 +1,113 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+
+public class ServletMapping
+{
+    private String[] _pathSpecs;
+    private String _servletName;
+    private boolean _default;
+    
+
+    /* ------------------------------------------------------------ */
+    public ServletMapping()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the pathSpecs.
+     */
+    public String[] getPathSpecs()
+    {
+        return _pathSpecs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the servletName.
+     */
+    public String getServletName()
+    {
+        return _servletName;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpecs The pathSpecs to set.
+     */
+    public void setPathSpecs(String[] pathSpecs)
+    {
+        _pathSpecs = pathSpecs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param pathSpec The pathSpec to set.
+     */
+    public void setPathSpec(String pathSpec)
+    {
+        _pathSpecs = new String[]{pathSpec};
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletName The servletName to set.
+     */
+    public void setServletName(String servletName)
+    {
+        _servletName = servletName;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return
+     */
+    public boolean isDefault()
+    {
+        return _default;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param default1
+     */
+    public void setDefault(boolean fromDefault)
+    {
+        _default = fromDefault;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return (_pathSpecs==null?"[]":Arrays.asList(_pathSpecs).toString())+"=>"+_servletName; 
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        out.append(String.valueOf(this)).append("\n");
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/StatisticsServlet.java b/src/java/org/eclipse/jetty/servlet/StatisticsServlet.java
new file mode 100644
index 0000000..6da92bb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/StatisticsServlet.java
@@ -0,0 +1,247 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.StatisticsHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+public class StatisticsServlet extends HttpServlet
+{
+    private static final Logger LOG = Log.getLogger(StatisticsServlet.class);
+
+    boolean _restrictToLocalhost = true; // defaults to true
+    private StatisticsHandler _statsHandler;
+    private MemoryMXBean _memoryBean;
+    private Connector[] _connectors;
+
+    public void init() throws ServletException
+    {
+        ServletContext context = getServletContext();
+        ContextHandler.Context scontext = (ContextHandler.Context) context;
+        Server _server = scontext.getContextHandler().getServer();
+
+        Handler handler = _server.getChildHandlerByClass(StatisticsHandler.class);
+
+        if (handler != null)
+        {
+            _statsHandler = (StatisticsHandler) handler;
+        }
+        else
+        {
+            LOG.warn("Statistics Handler not installed!");
+            return;
+        }
+        
+        _memoryBean = ManagementFactory.getMemoryMXBean();
+        _connectors = _server.getConnectors();
+
+        if (getInitParameter("restrictToLocalhost") != null)
+        {
+            _restrictToLocalhost = "true".equals(getInitParameter("restrictToLocalhost"));
+        }
+
+    }
+
+    public void doPost(HttpServletRequest sreq, HttpServletResponse sres) throws ServletException, IOException
+    {
+        doGet(sreq, sres);
+    }
+
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        if (_statsHandler == null)
+        {
+            LOG.warn("Statistics Handler not installed!");
+            resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            return;
+        }
+        if (_restrictToLocalhost)
+        {
+            if (!isLoopbackAddress(req.getRemoteAddr()))
+            {
+                resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return;
+            }
+        }
+
+        String wantXml = req.getParameter("xml");
+        if (wantXml == null)
+          wantXml = req.getParameter("XML");
+
+        if (wantXml != null && "true".equalsIgnoreCase(wantXml))
+        {
+            sendXmlResponse(resp);
+        }
+        else
+        {
+            sendTextResponse(resp);
+        }
+
+    }
+
+    private boolean isLoopbackAddress(String address)
+    {
+        try
+        {
+            InetAddress addr = InetAddress.getByName(address); 
+            return addr.isLoopbackAddress();
+        }
+        catch (UnknownHostException e )
+        {
+            LOG.warn("Warning: attempt to access statistics servlet from " + address, e);
+            return false;
+        }
+    }
+
+    private void sendXmlResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("<statistics>\n");
+
+        sb.append("  <requests>\n");
+        sb.append("    <statsOnMs>").append(_statsHandler.getStatsOnMs()).append("</statsOnMs>\n");
+        
+        sb.append("    <requests>").append(_statsHandler.getRequests()).append("</requests>\n");
+        sb.append("    <requestsActive>").append(_statsHandler.getRequestsActive()).append("</requestsActive>\n");
+        sb.append("    <requestsActiveMax>").append(_statsHandler.getRequestsActiveMax()).append("</requestsActiveMax>\n");
+        sb.append("    <requestsTimeTotal>").append(_statsHandler.getRequestTimeTotal()).append("</requestsTimeTotal>\n");
+        sb.append("    <requestsTimeMean>").append(_statsHandler.getRequestTimeMean()).append("</requestsTimeMean>\n");
+        sb.append("    <requestsTimeMax>").append(_statsHandler.getRequestTimeMax()).append("</requestsTimeMax>\n");
+        sb.append("    <requestsTimeStdDev>").append(_statsHandler.getRequestTimeStdDev()).append("</requestsTimeStdDev>\n");
+
+        sb.append("    <dispatched>").append(_statsHandler.getDispatched()).append("</dispatched>\n");
+        sb.append("    <dispatchedActive>").append(_statsHandler.getDispatchedActive()).append("</dispatchedActive>\n");
+        sb.append("    <dispatchedActiveMax>").append(_statsHandler.getDispatchedActiveMax()).append("</dispatchedActiveMax>\n");
+        sb.append("    <dispatchedTimeTotal>").append(_statsHandler.getDispatchedTimeTotal()).append("</dispatchedTimeTotal>\n");
+        sb.append("    <dispatchedTimeMean>").append(_statsHandler.getDispatchedTimeMean()).append("</dispatchedTimeMean>\n");
+        sb.append("    <dispatchedTimeMax>").append(_statsHandler.getDispatchedTimeMax()).append("</dispatchedTimeMax>\n");
+        sb.append("    <dispatchedTimeStdDev").append(_statsHandler.getDispatchedTimeStdDev()).append("</dispatchedTimeStdDev>\n");
+        
+        sb.append("    <requestsSuspended>").append(_statsHandler.getSuspends()).append("</requestsSuspended>\n");
+        sb.append("    <requestsExpired>").append(_statsHandler.getExpires()).append("</requestsExpired>\n");
+        sb.append("    <requestsResumed>").append(_statsHandler.getResumes()).append("</requestsResumed>\n");
+        sb.append("  </requests>\n");
+
+        sb.append("  <responses>\n");
+        sb.append("    <responses1xx>").append(_statsHandler.getResponses1xx()).append("</responses1xx>\n");
+        sb.append("    <responses2xx>").append(_statsHandler.getResponses2xx()).append("</responses2xx>\n");
+        sb.append("    <responses3xx>").append(_statsHandler.getResponses3xx()).append("</responses3xx>\n");
+        sb.append("    <responses4xx>").append(_statsHandler.getResponses4xx()).append("</responses4xx>\n");
+        sb.append("    <responses5xx>").append(_statsHandler.getResponses5xx()).append("</responses5xx>\n");
+        sb.append("    <responsesBytesTotal>").append(_statsHandler.getResponsesBytesTotal()).append("</responsesBytesTotal>\n");
+        sb.append("  </responses>\n");
+
+        sb.append("  <connections>\n");
+        for (Connector connector : _connectors)
+        {
+        	sb.append("    <connector>\n");
+        	sb.append("      <name>").append(connector.getName()).append("</name>\n");
+        	sb.append("      <statsOn>").append(connector.getStatsOn()).append("</statsOn>\n");
+            if (connector.getStatsOn())
+            {
+            	sb.append("    <statsOnMs>").append(connector.getStatsOnMs()).append("</statsOnMs>\n");
+            	sb.append("    <connections>").append(connector.getConnections()).append("</connections>\n");
+            	sb.append("    <connectionsOpen>").append(connector.getConnectionsOpen()).append("</connectionsOpen>\n");
+            	sb.append("    <connectionsOpenMax>").append(connector.getConnectionsOpenMax()).append("</connectionsOpenMax>\n");
+            	sb.append("    <connectionsDurationTotal>").append(connector.getConnectionsDurationTotal()).append("</connectionsDurationTotal>\n");
+            	sb.append("    <connectionsDurationMean>").append(connector.getConnectionsDurationMean()).append("</connectionsDurationMean>\n");
+            	sb.append("    <connectionsDurationMax>").append(connector.getConnectionsDurationMax()).append("</connectionsDurationMax>\n");
+                sb.append("    <connectionsDurationStdDev>").append(connector.getConnectionsDurationStdDev()).append("</connectionsDurationStdDev>\n");
+                sb.append("    <requests>").append(connector.getRequests()).append("</requests>\n");
+                sb.append("    <connectionsRequestsMean>").append(connector.getConnectionsRequestsMean()).append("</connectionsRequestsMean>\n");
+                sb.append("    <connectionsRequestsMax>").append(connector.getConnectionsRequestsMax()).append("</connectionsRequestsMax>\n");
+                sb.append("    <connectionsRequestsStdDev>").append(connector.getConnectionsRequestsStdDev()).append("</connectionsRequestsStdDev>\n");
+            }
+            sb.append("    </connector>\n");
+        }
+        sb.append("  </connections>\n");
+
+        sb.append("  <memory>\n");
+        sb.append("    <heapMemoryUsage>").append(_memoryBean.getHeapMemoryUsage().getUsed()).append("</heapMemoryUsage>\n");
+        sb.append("    <nonHeapMemoryUsage>").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append("</nonHeapMemoryUsage>\n");
+        sb.append("  </memory>\n");
+
+        sb.append("</statistics>\n");
+
+        response.setContentType("text/xml");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+    }
+
+    private void sendTextResponse(HttpServletResponse response) throws IOException
+    {
+        StringBuilder sb = new StringBuilder();
+        sb.append(_statsHandler.toStatsHTML());
+
+        sb.append("<h2>Connections:</h2>\n");
+        for (Connector connector : _connectors)
+        {
+            sb.append("<h3>").append(connector.getName()).append("</h3>");
+
+            if (connector.getStatsOn())
+            {
+                sb.append("Statistics gathering started ").append(connector.getStatsOnMs()).append("ms ago").append("<br />\n");
+                sb.append("Total connections: ").append(connector.getConnections()).append("<br />\n");
+                sb.append("Current connections open: ").append(connector.getConnectionsOpen()).append("<br />\n");
+                sb.append("Max concurrent connections open: ").append(connector.getConnectionsOpenMax()).append("<br />\n");
+                sb.append("Total connections duration: ").append(connector.getConnectionsDurationTotal()).append("<br />\n");
+                sb.append("Mean connection duration: ").append(connector.getConnectionsDurationMean()).append("<br />\n");
+                sb.append("Max connection duration: ").append(connector.getConnectionsDurationMax()).append("<br />\n");
+                sb.append("Connection duration standard deviation: ").append(connector.getConnectionsDurationStdDev()).append("<br />\n");
+                sb.append("Total requests: ").append(connector.getRequests()).append("<br />\n");
+                sb.append("Mean requests per connection: ").append(connector.getConnectionsRequestsMean()).append("<br />\n");
+                sb.append("Max requests per connection: ").append(connector.getConnectionsRequestsMax()).append("<br />\n");
+                sb.append("Requests per connection standard deviation: ").append(connector.getConnectionsRequestsStdDev()).append("<br />\n");
+            }
+            else
+            {
+                sb.append("Statistics gathering off.\n");
+            }
+
+        }
+
+        sb.append("<h2>Memory:</h2>\n");
+        sb.append("Heap memory usage: ").append(_memoryBean.getHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+        sb.append("Non-heap memory usage: ").append(_memoryBean.getNonHeapMemoryUsage().getUsed()).append(" bytes").append("<br />\n");
+
+        response.setContentType("text/html");
+        PrintWriter pout = response.getWriter();
+        pout.write(sb.toString());
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/jmx/FilterMappingMBean.java b/src/java/org/eclipse/jetty/servlet/jmx/FilterMappingMBean.java
new file mode 100644
index 0000000..5521edc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/jmx/FilterMappingMBean.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.servlet.FilterMapping;
+
+public class FilterMappingMBean extends ObjectMBean
+{
+
+    public FilterMappingMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    public String getObjectNameBasis()
+    {
+        if (_managed != null && _managed instanceof FilterMapping)
+        {
+            FilterMapping mapping = (FilterMapping)_managed;
+            String name = mapping.getFilterName();
+            if (name != null)
+                return name;
+        }
+        
+        return super.getObjectNameBasis();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/jmx/HolderMBean.java b/src/java/org/eclipse/jetty/servlet/jmx/HolderMBean.java
new file mode 100644
index 0000000..5e1ec23
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/jmx/HolderMBean.java
@@ -0,0 +1,43 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.servlet.Holder;
+
+public class HolderMBean extends ObjectMBean
+{
+    public HolderMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getObjectNameBasis()
+    {
+        if (_managed!=null && _managed instanceof Holder)
+        {
+            Holder holder = (Holder)_managed;
+            String name = holder.getName();
+            if (name!=null)
+                return name;
+        }
+        return super.getObjectNameBasis();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/jmx/ServletMappingMBean.java b/src/java/org/eclipse/jetty/servlet/jmx/ServletMappingMBean.java
new file mode 100644
index 0000000..a7c9898
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/jmx/ServletMappingMBean.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.jmx;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.servlet.ServletMapping;
+
+public class ServletMappingMBean extends ObjectMBean
+{
+
+    public ServletMappingMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    public String getObjectNameBasis()
+    {
+        if (_managed != null && _managed instanceof ServletMapping)
+        {
+            ServletMapping mapping = (ServletMapping)_managed;
+            String name = mapping.getServletName();
+            if (name != null)
+                return name;
+        }
+        
+        return super.getObjectNameBasis();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java b/src/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
new file mode 100644
index 0000000..6af3c8e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/listener/ELContextCleaner.java
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * ELContextCleaner
+ *
+ * Clean up BeanELResolver when the context is going out
+ * of service:
+ * 
+ * See http://java.net/jira/browse/GLASSFISH-1649
+ * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=353095
+ */
+public class ELContextCleaner implements ServletContextListener
+{
+    private static final Logger LOG = Log.getLogger(ELContextCleaner.class);
+
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        try
+        {
+            //Check that the BeanELResolver class is on the classpath
+            Class beanELResolver = Loader.loadClass(this.getClass(), "javax.el.BeanELResolver");
+
+            //Get a reference via reflection to the properties field which is holding class references
+            Field field = getField(beanELResolver);
+
+            //Get rid of references
+            purgeEntries(field);
+            
+            LOG.info("javax.el.BeanELResolver purged");
+        }
+        
+        catch (ClassNotFoundException e)
+        {
+            //BeanELResolver not on classpath, ignore
+        }
+        catch (SecurityException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalArgumentException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (IllegalAccessException e)
+        {
+            LOG.warn("Cannot purge classes from javax.el.BeanELResolver", e);
+        }
+        catch (NoSuchFieldException e)
+        {
+            LOG.info("Not cleaning cached beans: no such field javax.el.BeanELResolver.properties");
+        }
+       
+    }
+
+
+    protected Field getField (Class beanELResolver) 
+    throws SecurityException, NoSuchFieldException
+    {
+        if (beanELResolver == null)
+            return  null;
+
+        return beanELResolver.getDeclaredField("properties");
+    }
+
+    protected void purgeEntries (Field properties) 
+    throws IllegalArgumentException, IllegalAccessException
+    {
+        if (properties == null)
+            return;
+
+        if (!properties.isAccessible())
+            properties.setAccessible(true);
+
+        ConcurrentHashMap map = (ConcurrentHashMap) properties.get(null);
+        if (map == null)
+            return;
+        
+        Iterator<Class> itor = map.keySet().iterator();
+        while (itor.hasNext()) 
+        {
+            Class clazz = itor.next();
+            LOG.info("Clazz: "+clazz+" loaded by "+clazz.getClassLoader());
+            if (Thread.currentThread().getContextClassLoader().equals(clazz.getClassLoader()))
+            {
+                itor.remove();  
+                LOG.info("removed");
+            }
+            else
+                LOG.info("not removed: "+"contextclassloader="+Thread.currentThread().getContextClassLoader()+"clazz's classloader="+clazz.getClassLoader());
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java b/src/java/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java
new file mode 100644
index 0000000..72a6f0d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlet/listener/IntrospectorCleaner.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlet.listener;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+/**
+ * IntrospectorCleaner
+ *
+ * Cleans a static cache of Methods held by java.beans.Introspector
+ * class when a context is undeployed.
+ * 
+ * @see java.beans.Introspector
+ */
+public class IntrospectorCleaner implements ServletContextListener
+{
+
+    public void contextInitialized(ServletContextEvent sce)
+    {
+        
+    }
+
+    public void contextDestroyed(ServletContextEvent sce)
+    {
+        java.beans.Introspector.flushCaches();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlets/BalancerServlet.java b/src/java/org/eclipse/jetty/servlets/BalancerServlet.java
new file mode 100644
index 0000000..f3e6140
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/BalancerServlet.java
@@ -0,0 +1,422 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.server.Request;
+
+/**
+ * 6
+ */
+public class BalancerServlet extends ProxyServlet
+{
+
+    private static final class BalancerMember
+    {
+
+        private String _name;
+
+        private String _proxyTo;
+
+        private HttpURI _backendURI;
+
+        public BalancerMember(String name, String proxyTo)
+        {
+            super();
+            _name = name;
+            _proxyTo = proxyTo;
+            _backendURI = new HttpURI(_proxyTo);
+        }
+
+        public String getProxyTo()
+        {
+            return _proxyTo;
+        }
+
+        public HttpURI getBackendURI()
+        {
+            return _backendURI;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "BalancerMember [_name=" + _name + ", _proxyTo=" + _proxyTo + "]";
+        }
+
+        @Override
+        public int hashCode()
+        {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((_name == null)?0:_name.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            BalancerMember other = (BalancerMember)obj;
+            if (_name == null)
+            {
+                if (other._name != null)
+                    return false;
+            }
+            else if (!_name.equals(other._name))
+                return false;
+            return true;
+        }
+
+    }
+
+    private static final class RoundRobinIterator implements Iterator<BalancerMember>
+    {
+
+        private BalancerMember[] _balancerMembers;
+
+        private AtomicInteger _index;
+
+        public RoundRobinIterator(Collection<BalancerMember> balancerMembers)
+        {
+            _balancerMembers = (BalancerMember[])balancerMembers.toArray(new BalancerMember[balancerMembers.size()]);
+            _index = new AtomicInteger(-1);
+        }
+
+        public boolean hasNext()
+        {
+            return true;
+        }
+
+        public BalancerMember next()
+        {
+            BalancerMember balancerMember = null;
+            while (balancerMember == null)
+            {
+                int currentIndex = _index.get();
+                int nextIndex = (currentIndex + 1) % _balancerMembers.length;
+                if (_index.compareAndSet(currentIndex,nextIndex))
+                {
+                    balancerMember = _balancerMembers[nextIndex];
+                }
+            }
+            return balancerMember;
+        }
+
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    private static final String BALANCER_MEMBER_PREFIX = "BalancerMember.";
+
+    private static final List<String> FORBIDDEN_CONFIG_PARAMETERS;
+    static
+    {
+        List<String> params = new LinkedList<String>();
+        params.add("HostHeader");
+        params.add("whiteList");
+        params.add("blackList");
+        FORBIDDEN_CONFIG_PARAMETERS = Collections.unmodifiableList(params);
+    }
+
+    private static final List<String> REVERSE_PROXY_HEADERS;
+    static
+    {
+        List<String> params = new LinkedList<String>();
+        params.add("Location");
+        params.add("Content-Location");
+        params.add("URI");
+        REVERSE_PROXY_HEADERS = Collections.unmodifiableList(params);
+    }
+
+    private static final String JSESSIONID = "jsessionid";
+
+    private static final String JSESSIONID_URL_PREFIX = JSESSIONID + "=";
+
+    private boolean _stickySessions;
+
+    private Set<BalancerMember> _balancerMembers = new HashSet<BalancerMember>();
+
+    private boolean _proxyPassReverse;
+
+    private RoundRobinIterator _roundRobinIterator;
+
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        validateConfig(config);
+        super.init(config);
+        initStickySessions(config);
+        initBalancers(config);
+        initProxyPassReverse(config);
+        postInit();
+    }
+
+    private void validateConfig(ServletConfig config) throws ServletException
+    {
+        @SuppressWarnings("unchecked")
+        List<String> initParameterNames = Collections.list(config.getInitParameterNames());
+        for (String initParameterName : initParameterNames)
+        {
+            if (FORBIDDEN_CONFIG_PARAMETERS.contains(initParameterName))
+            {
+                throw new UnavailableException(initParameterName + " not supported in " + getClass().getName());
+            }
+        }
+    }
+
+    private void initStickySessions(ServletConfig config) throws ServletException
+    {
+        _stickySessions = "true".equalsIgnoreCase(config.getInitParameter("StickySessions"));
+    }
+
+    private void initBalancers(ServletConfig config) throws ServletException
+    {
+        Set<String> balancerNames = getBalancerNames(config);
+        for (String balancerName : balancerNames)
+        {
+            String memberProxyToParam = BALANCER_MEMBER_PREFIX + balancerName + ".ProxyTo";
+            String proxyTo = config.getInitParameter(memberProxyToParam);
+            if (proxyTo == null || proxyTo.trim().length() == 0)
+            {
+                throw new UnavailableException(memberProxyToParam + " parameter is empty.");
+            }
+            _balancerMembers.add(new BalancerMember(balancerName,proxyTo));
+        }
+    }
+
+    private void initProxyPassReverse(ServletConfig config)
+    {
+        _proxyPassReverse = "true".equalsIgnoreCase(config.getInitParameter("ProxyPassReverse"));
+    }
+
+    private void postInit()
+    {
+        _roundRobinIterator = new RoundRobinIterator(_balancerMembers);
+    }
+
+    private Set<String> getBalancerNames(ServletConfig config) throws ServletException
+    {
+        Set<String> names = new HashSet<String>();
+        @SuppressWarnings("unchecked")
+        List<String> initParameterNames = Collections.list(config.getInitParameterNames());
+        for (String initParameterName : initParameterNames)
+        {
+            if (!initParameterName.startsWith(BALANCER_MEMBER_PREFIX))
+            {
+                continue;
+            }
+            int endOfNameIndex = initParameterName.lastIndexOf(".");
+            if (endOfNameIndex <= BALANCER_MEMBER_PREFIX.length())
+            {
+                throw new UnavailableException(initParameterName + " parameter does not provide a balancer member name");
+            }
+            names.add(initParameterName.substring(BALANCER_MEMBER_PREFIX.length(),endOfNameIndex));
+        }
+        return names;
+    }
+
+    @Override
+    protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
+    {
+        BalancerMember balancerMember = selectBalancerMember(request);
+        try
+        {
+            URI dstUri = new URI(balancerMember.getProxyTo() + "/" + uri).normalize();
+            return new HttpURI(dstUri.toString());
+        }
+        catch (URISyntaxException e)
+        {
+            throw new MalformedURLException(e.getMessage());
+        }
+    }
+
+    private BalancerMember selectBalancerMember(HttpServletRequest request)
+    {
+        BalancerMember balancerMember = null;
+        if (_stickySessions)
+        {
+            String name = getBalancerMemberNameFromSessionId(request);
+            if (name != null)
+            {
+                balancerMember = findBalancerMemberByName(name);
+                if (balancerMember != null)
+                {
+                    return balancerMember;
+                }
+            }
+        }
+        return _roundRobinIterator.next();
+    }
+
+    private BalancerMember findBalancerMemberByName(String name)
+    {
+        BalancerMember example = new BalancerMember(name,"");
+        for (BalancerMember balancerMember : _balancerMembers)
+        {
+            if (balancerMember.equals(example))
+            {
+                return balancerMember;
+            }
+        }
+        return null;
+    }
+
+    private String getBalancerMemberNameFromSessionId(HttpServletRequest request)
+    {
+        String name = getBalancerMemberNameFromSessionCookie(request);
+        if (name == null)
+        {
+            name = getBalancerMemberNameFromURL(request);
+        }
+        return name;
+    }
+
+    private String getBalancerMemberNameFromSessionCookie(HttpServletRequest request)
+    {
+        Cookie[] cookies = request.getCookies();
+        String name = null;
+        for (Cookie cookie : cookies)
+        {
+            if (JSESSIONID.equalsIgnoreCase(cookie.getName()))
+            {
+                name = extractBalancerMemberNameFromSessionId(cookie.getValue());
+                break;
+            }
+        }
+        return name;
+    }
+
+    private String getBalancerMemberNameFromURL(HttpServletRequest request)
+    {
+        String name = null;
+        String requestURI = request.getRequestURI();
+        int idx = requestURI.lastIndexOf(";");
+        if (idx != -1)
+        {
+            String requestURISuffix = requestURI.substring(idx);
+            if (requestURISuffix.startsWith(JSESSIONID_URL_PREFIX))
+            {
+                name = extractBalancerMemberNameFromSessionId(requestURISuffix.substring(JSESSIONID_URL_PREFIX.length()));
+            }
+        }
+        return name;
+    }
+
+    private String extractBalancerMemberNameFromSessionId(String sessionId)
+    {
+        String name = null;
+        int idx = sessionId.lastIndexOf(".");
+        if (idx != -1)
+        {
+            String sessionIdSuffix = sessionId.substring(idx + 1);
+            name = (sessionIdSuffix.length() > 0)?sessionIdSuffix:null;
+        }
+        return name;
+    }
+
+    @Override
+    protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
+    {
+        if (_proxyPassReverse && REVERSE_PROXY_HEADERS.contains(headerName))
+        {
+            HttpURI locationURI = new HttpURI(headerValue);
+            if (isAbsoluteLocation(locationURI) && isBackendLocation(locationURI))
+            {
+                Request jettyRequest = (Request)request;
+                URI reverseUri;
+                try
+                {
+                    reverseUri = new URI(jettyRequest.getRootURL().append(locationURI.getCompletePath()).toString()).normalize();
+                    return reverseUri.toURL().toString();
+                }
+                catch (Exception e)
+                {
+                    _log.warn("Not filtering header response",e);
+                    return headerValue;
+                }
+            }
+        }
+        return headerValue;
+    }
+
+    private boolean isBackendLocation(HttpURI locationURI)
+    {
+        for (BalancerMember balancerMember : _balancerMembers)
+        {
+            HttpURI backendURI = balancerMember.getBackendURI();
+            if (backendURI.getHost().equals(locationURI.getHost()) && backendURI.getScheme().equals(locationURI.getScheme())
+                    && backendURI.getPort() == locationURI.getPort())
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isAbsoluteLocation(HttpURI locationURI)
+    {
+        return locationURI.getHost() != null;
+    }
+
+    @Override
+    public String getHostHeader()
+    {
+        throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
+    }
+
+    @Override
+    public void setHostHeader(String hostHeader)
+    {
+        throw new UnsupportedOperationException("HostHeader not supported in " + getClass().getName());
+    }
+
+    @Override
+    public boolean validateDestination(String host, String path)
+    {
+        return true;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlets/CGI.java b/src/java/org/eclipse/jetty/servlets/CGI.java
new file mode 100644
index 0000000..83b2457
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/CGI.java
@@ -0,0 +1,515 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethods;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.UrlEncoded;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+//-----------------------------------------------------------------------------
+/**
+ * CGI Servlet.
+ * <p/>
+ * The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the
+ * "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work.
+ * <br/>
+ * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files.
+ * <p/>
+ * The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
+ * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
+ * <p/>
+ * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem.
+ * <p/>
+ * Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value.
+ */
+public class CGI extends HttpServlet
+{
+    /**
+     *
+     */
+    private static final long serialVersionUID = -6182088932884791073L;
+
+    private static final Logger LOG = Log.getLogger(CGI.class);
+
+    private boolean _ok;
+    private File _docRoot;
+    private String _path;
+    private String _cmdPrefix;
+    private EnvList _env;
+    private boolean _ignoreExitState;
+    private boolean _relative;
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void init() throws ServletException
+    {
+        _env = new EnvList();
+        _cmdPrefix = getInitParameter("commandPrefix");
+        _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
+
+        String tmp = getInitParameter("cgibinResourceBase");
+        if (tmp == null)
+        {
+            tmp = getInitParameter("resourceBase");
+            if (tmp == null)
+                tmp = getServletContext().getRealPath("/");
+        }
+        else if (_relative)
+        {
+            tmp = getServletContext().getRealPath(tmp);
+        }
+
+        if (tmp == null)
+        {
+            LOG.warn("CGI: no CGI bin !");
+            return;
+        }
+
+        File dir = new File(tmp);
+        if (!dir.exists())
+        {
+            LOG.warn("CGI: CGI bin does not exist - " + dir);
+            return;
+        }
+
+        if (!dir.canRead())
+        {
+            LOG.warn("CGI: CGI bin is not readable - " + dir);
+            return;
+        }
+
+        if (!dir.isDirectory())
+        {
+            LOG.warn("CGI: CGI bin is not a directory - " + dir);
+            return;
+        }
+
+        try
+        {
+            _docRoot = dir.getCanonicalFile();
+        }
+        catch (IOException e)
+        {
+            LOG.warn("CGI: CGI bin failed - " + dir,e);
+            return;
+        }
+
+        _path = getInitParameter("Path");
+        if (_path != null)
+            _env.set("PATH",_path);
+
+        _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
+        Enumeration e = getInitParameterNames();
+        while (e.hasMoreElements())
+        {
+            String n = (String)e.nextElement();
+            if (n != null && n.startsWith("ENV_"))
+                _env.set(n.substring(4),getInitParameter(n));
+        }
+        if (!_env.envMap.containsKey("SystemRoot"))
+        {
+            String os = System.getProperty("os.name");
+            if (os != null && os.toLowerCase(Locale.ENGLISH).indexOf("windows") != -1)
+            {
+                _env.set("SystemRoot","C:\\WINDOWS");
+            }
+        }
+
+        _ok = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
+    {
+        if (!_ok)
+        {
+            res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            return;
+        }
+
+        String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("CGI: ContextPath : " + req.getContextPath());
+            LOG.debug("CGI: ServletPath : " + req.getServletPath());
+            LOG.debug("CGI: PathInfo    : " + req.getPathInfo());
+            LOG.debug("CGI: _docRoot    : " + _docRoot);
+            LOG.debug("CGI: _path       : " + _path);
+            LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState);
+        }
+
+        // pathInContext may actually comprises scriptName/pathInfo...We will
+        // walk backwards up it until we find the script - the rest must
+        // be the pathInfo;
+
+        String both = pathInContext;
+        String first = both;
+        String last = "";
+
+        File exe = new File(_docRoot,first);
+
+        while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0)
+        {
+            int index = first.lastIndexOf('/');
+
+            first = first.substring(0,index);
+            last = both.substring(index,both.length());
+            exe = new File(_docRoot,first);
+        }
+
+        if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath()))
+        {
+            res.sendError(404);
+        }
+        else
+        {
+            if (LOG.isDebugEnabled())
+            {
+                LOG.debug("CGI: script is " + exe);
+                LOG.debug("CGI: pathInfo is " + last);
+            }
+            exec(exe,last,req,res);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @param root @param path @param req @param res @exception IOException
+     */
+    private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
+    {
+        String path = command.getAbsolutePath();
+        File dir = command.getParentFile();
+        String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
+        String scriptPath = getServletContext().getRealPath(scriptName);
+        String pathTranslated = req.getPathTranslated();
+
+        int len = req.getContentLength();
+        if (len < 0)
+            len = 0;
+        if ((pathTranslated == null) || (pathTranslated.length() == 0))
+            pathTranslated = path;
+
+        String bodyFormEncoded = null;
+        if ((HttpMethods.POST.equals(req.getMethod()) || HttpMethods.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType()))
+        {
+            MultiMap<String> parameterMap = new MultiMap<String>();
+            Enumeration names = req.getParameterNames();
+            while (names.hasMoreElements())
+            {
+                String parameterName = (String)names.nextElement();
+                parameterMap.addValues(parameterName, req.getParameterValues(parameterName));
+            }
+            bodyFormEncoded = UrlEncoded.encode(parameterMap, req.getCharacterEncoding(), true);
+        }
+
+        EnvList env = new EnvList(_env);
+        // these ones are from "The WWW Common Gateway Interface Version 1.1"
+        // look at :
+        // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
+        env.set("AUTH_TYPE", req.getAuthType());
+        if (bodyFormEncoded != null)
+        {
+            env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length()));
+        }
+        else
+        {
+            env.set("CONTENT_LENGTH", Integer.toString(len));
+        }
+        env.set("CONTENT_TYPE", req.getContentType());
+        env.set("GATEWAY_INTERFACE", "CGI/1.1");
+        if ((pathInfo != null) && (pathInfo.length() > 0))
+        {
+            env.set("PATH_INFO", pathInfo);
+        }
+        env.set("PATH_TRANSLATED", pathTranslated);
+        env.set("QUERY_STRING", req.getQueryString());
+        env.set("REMOTE_ADDR", req.getRemoteAddr());
+        env.set("REMOTE_HOST", req.getRemoteHost());
+        // The identity information reported about the connection by a
+        // RFC 1413 [11] request to the remote agent, if
+        // available. Servers MAY choose not to support this feature, or
+        // not to request the data for efficiency reasons.
+        // "REMOTE_IDENT" => "NYI"
+        env.set("REMOTE_USER", req.getRemoteUser());
+        env.set("REQUEST_METHOD", req.getMethod());
+        env.set("SCRIPT_NAME", scriptName);
+        env.set("SCRIPT_FILENAME", scriptPath);
+        env.set("SERVER_NAME", req.getServerName());
+        env.set("SERVER_PORT", Integer.toString(req.getServerPort()));
+        env.set("SERVER_PROTOCOL", req.getProtocol());
+        env.set("SERVER_SOFTWARE", getServletContext().getServerInfo());
+
+        Enumeration enm = req.getHeaderNames();
+        while (enm.hasMoreElements())
+        {
+            String name = (String)enm.nextElement();
+            String value = req.getHeader(name);
+            env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value);
+        }
+
+        // these extra ones were from printenv on www.dev.nomura.co.uk
+        env.set("HTTPS", (req.isSecure()?"ON":"OFF"));
+        // "DOCUMENT_ROOT" => root + "/docs",
+        // "SERVER_URL" => "NYI - http://us0245",
+        // "TZ" => System.getProperty("user.timezone"),
+
+        // are we meant to decode args here ? or does the script get them
+        // via PATH_INFO ? if we are, they should be decoded and passed
+        // into exec here...
+        String execCmd = path;
+        if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
+            execCmd = "\"" + execCmd + "\"";
+        if (_cmdPrefix != null)
+            execCmd = _cmdPrefix + " " + execCmd;
+
+        LOG.debug("Environment: " + env.getExportString());
+        LOG.debug("Command: " + execCmd);
+
+        Process p;
+        if (dir == null)
+            p = Runtime.getRuntime().exec(execCmd, env.getEnvArray());
+        else
+            p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir);
+
+        // hook processes input to browser's output (async)
+        if (bodyFormEncoded != null)
+            writeProcessInput(p, bodyFormEncoded);
+        else if (len > 0)
+            writeProcessInput(p, req.getInputStream(), len);
+
+        IO.copyThread(p.getErrorStream(), System.err);
+
+        // hook processes output to browser's input (sync)
+        // if browser closes stream, we should detect it and kill process...
+        OutputStream os = null;
+        try
+        {
+            // read any headers off the top of our input stream
+            // NOTE: Multiline header items not supported!
+            String line = null;
+            InputStream inFromCgi = p.getInputStream();
+
+            // br=new BufferedReader(new InputStreamReader(inFromCgi));
+            // while ((line=br.readLine())!=null)
+            while ((line = getTextLineFromStream(inFromCgi)).length() > 0)
+            {
+                if (!line.startsWith("HTTP"))
+                {
+                    int k = line.indexOf(':');
+                    if (k > 0)
+                    {
+                        String key = line.substring(0,k).trim();
+                        String value = line.substring(k + 1).trim();
+                        if ("Location".equals(key))
+                        {
+                            res.sendRedirect(res.encodeRedirectURL(value));
+                        }
+                        else if ("Status".equals(key))
+                        {
+                            String[] token = value.split(" ");
+                            int status = Integer.parseInt(token[0]);
+                            res.setStatus(status);
+                        }
+                        else
+                        {
+                            // add remaining header items to our response header
+                            res.addHeader(key,value);
+                        }
+                    }
+                }
+            }
+            // copy cgi content to response stream...
+            os = res.getOutputStream();
+            IO.copy(inFromCgi,os);
+            p.waitFor();
+
+            if (!_ignoreExitState)
+            {
+                int exitValue = p.exitValue();
+                if (0 != exitValue)
+                {
+                    LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path);
+                    if (!res.isCommitted())
+                        res.sendError(500,"Failed to exec CGI");
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            // browser has probably closed its input stream - we
+            // terminate and clean up...
+            LOG.debug("CGI: Client closed connection!");
+        }
+        catch (InterruptedException ie)
+        {
+            LOG.debug("CGI: interrupted!");
+        }
+        finally
+        {
+            if (os != null)
+            {
+                try
+                {
+                    os.close();
+                }
+                catch (Exception e)
+                {
+                    LOG.debug(e);
+                }
+            }
+            p.destroy();
+            // LOG.debug("CGI: terminated!");
+        }
+    }
+
+    private static void writeProcessInput(final Process p, final String input)
+    {
+        new Thread(new Runnable()
+        {
+            public void run()
+            {
+                try
+                {
+                    Writer outToCgi = new OutputStreamWriter(p.getOutputStream());
+                    outToCgi.write(input);
+                    outToCgi.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.debug(e);
+                }
+            }
+        }).start();
+    }
+
+    private static void writeProcessInput(final Process p, final InputStream input, final int len)
+    {
+        if (len <= 0) return;
+
+        new Thread(new Runnable()
+        {
+            public void run()
+            {
+                try
+                {
+                    OutputStream outToCgi = p.getOutputStream();
+                    IO.copy(input, outToCgi, len);
+                    outToCgi.close();
+                }
+                catch (IOException e)
+                {
+                    LOG.debug(e);
+                }
+            }
+        }).start();
+    }
+
+    /**
+     * Utility method to get a line of text from the input stream.
+     *
+     * @param is
+     *            the input stream
+     * @return the line of text
+     * @throws IOException
+     */
+    private static String getTextLineFromStream(InputStream is) throws IOException
+    {
+        StringBuilder buffer = new StringBuilder();
+        int b;
+
+        while ((b = is.read()) != -1 && b != '\n')
+        {
+            buffer.append((char)b);
+        }
+        return buffer.toString().trim();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * private utility class that manages the Environment passed to exec.
+     */
+    private static class EnvList
+    {
+        private Map<String, String> envMap;
+
+        EnvList()
+        {
+            envMap = new HashMap<String, String>();
+        }
+
+        EnvList(EnvList l)
+        {
+            envMap = new HashMap<String,String>(l.envMap);
+        }
+
+        /**
+         * Set a name/value pair, null values will be treated as an empty String
+         */
+        public void set(String name, String value)
+        {
+            envMap.put(name,name + "=" + StringUtil.nonNull(value));
+        }
+
+        /** Get representation suitable for passing to exec. */
+        public String[] getEnvArray()
+        {
+            return envMap.values().toArray(new String[envMap.size()]);
+        }
+
+        public String getExportString()
+        {
+            StringBuilder sb = new StringBuilder();
+            for (String variable : getEnvArray())
+            {
+                sb.append("export \"");
+                sb.append(variable);
+                sb.append("\"; ");
+            }
+            return sb.toString();
+        }
+
+        @Override
+        public String toString()
+        {
+            return envMap.toString();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java b/src/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java
new file mode 100644
index 0000000..90f780b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/CloseableDoSFilter.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Closeable DoS Filter.
+ * This is an extension to the {@link DoSFilter} that uses Jetty APIs to allow
+ * connections to be closed cleanly. 
+ */
+
+public class CloseableDoSFilter extends DoSFilter
+{
+    private static final Logger LOG = Log.getLogger(CloseableDoSFilter.class);
+
+    protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
+    {
+        try
+        {
+            Request base_request=(request instanceof Request)?(Request)request:AbstractHttpConnection.getCurrentConnection().getRequest();
+            base_request.getConnection().getEndPoint().close();
+        }
+        catch(IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/ConcatServlet.java b/src/java/org/eclipse/jetty/servlets/ConcatServlet.java
new file mode 100644
index 0000000..d466813
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/ConcatServlet.java
@@ -0,0 +1,125 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/* ------------------------------------------------------------ */
+/** Concatenation Servlet
+ * This servlet may be used to concatenate multiple resources into
+ * a single response.  It is intended to be used to load multiple
+ * javascript or css files, but may be used for any content of the 
+ * same mime type that can be meaningfully concatenated.
+ * <p>
+ * The servlet uses {@link RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
+ * to combine the requested content, so dynamically generated content
+ * may be combined (Eg engine.js for DWR).
+ * <p>
+ * The servlet uses parameter names of the query string as resource names
+ * relative to the context root.  So these script tags:
+ * <pre>
+ *  &lt;script type="text/javascript" src="../js/behaviour.js"&gt;&lt;/script&gt;
+ *  &lt;script type="text/javascript" src="../js/ajax.js&/chat/chat.js"&gt;&lt;/script&gt;
+ *  &lt;script type="text/javascript" src="../chat/chat.js"&gt;&lt;/script&gt;
+ * </pre> can be replaced with the single tag (with the ConcatServlet mapped to /concat):
+ * <pre>
+ *  &lt;script type="text/javascript" src="../concat?/js/behaviour.js&/js/ajax.js&/chat/chat.js"&gt;&lt;/script&gt;
+ * </pre>
+ * The {@link ServletContext#getMimeType(String)} method is used to determine the 
+ * mime type of each resource.  If the types of all resources do not match, then a 415 
+ * UNSUPPORTED_MEDIA_TYPE error is returned.
+ * <p>
+ * If the init parameter "development" is set to "true" then the servlet will run in
+ * development mode and the content will be concatenated on every request. Otherwise
+ * the init time of the servlet is used as the lastModifiedTime of the combined content
+ * and If-Modified-Since requests are handled with 206 NOT Modified responses if 
+ * appropriate. This means that when not in development mode, the servlet must be 
+ * restarted before changed content will be served.
+ * 
+ * 
+ *
+ */
+public class ConcatServlet extends HttpServlet
+{
+    boolean _development;
+    long _lastModified;
+    ServletContext _context;
+
+    /* ------------------------------------------------------------ */
+    public void init() throws ServletException
+    {
+        _lastModified=System.currentTimeMillis();
+        _context=getServletContext();   
+        _development="true".equals(getInitParameter("development"));
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @return The start time of the servlet unless in development mode, in which case -1 is returned.
+     */
+    protected long getLastModified(HttpServletRequest req)
+    {
+        return _development?-1:_lastModified;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+    {
+        String q=req.getQueryString();
+        if (q==null)
+        {
+            resp.sendError(HttpServletResponse.SC_NO_CONTENT);
+            return;
+        }
+        
+        String[] parts = q.split("\\&");
+        String type=null;
+        for (int i=0;i<parts.length;i++)
+        {
+            String t = _context.getMimeType(parts[i]);
+            if (t!=null)
+            {
+                if (type==null)
+                    type=t;
+                else if (!type.equals(t))
+                {
+                    resp.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
+                    return;
+                }
+            }   
+        }
+
+        if (type!=null)
+            resp.setContentType(type);
+
+        for (int i=0;i<parts.length;i++)
+        {
+            RequestDispatcher dispatcher=_context.getRequestDispatcher(parts[i]);
+            if (dispatcher!=null)
+                dispatcher.include(req,resp);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/src/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
new file mode 100644
index 0000000..3c28b69
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/CrossOriginFilter.java
@@ -0,0 +1,426 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * <p>Implementation of the
+ * <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing</a>.</p>
+ * <p>A typical example is to use this filter to allow cross-domain
+ * <a href="http://cometd.org">cometd</a> communication using the standard
+ * long polling transport instead of the JSONP transport (that is less
+ * efficient and less reactive to failures).</p>
+ * <p>This filter allows the following configuration parameters:
+ * <ul>
+ * <li><b>allowedOrigins</b>, a comma separated list of origins that are
+ * allowed to access the resources. Default value is <b>*</b>, meaning all
+ * origins.<br />
+ * If an allowed origin contains one or more * characters (for example
+ * http://*.domain.com), then "*" characters are converted to ".*", "."
+ * characters are escaped to "\." and the resulting allowed origin
+ * interpreted as a regular expression.<br />
+ * Allowed origins can therefore be more complex expressions such as
+ * https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains
+ * and any 3 letter top-level domain (.com, .net, .org, etc.).</li>
+ * <li><b>allowedMethods</b>, a comma separated list of HTTP methods that
+ * are allowed to be used when accessing the resources. Default value is
+ * <b>GET,POST,HEAD</b></li>
+ * <li><b>allowedHeaders</b>, a comma separated list of HTTP headers that
+ * are allowed to be specified when accessing the resources. Default value
+ * is <b>X-Requested-With,Content-Type,Accept,Origin</b></li>
+ * <li><b>preflightMaxAge</b>, the number of seconds that preflight requests
+ * can be cached by the client. Default value is <b>1800</b> seconds, or 30
+ * minutes</li>
+ * <li><b>allowCredentials</b>, a boolean indicating if the resource allows
+ * requests with credentials. Default value is <b>false</b></li>
+ * <li><b>exposeHeaders</b>, a comma separated list of HTTP headers that
+ * are allowed to be exposed on the client. Default value is the
+ * <b>empty list</b></li>
+ * <li><b>chainPreflight</b>, if true preflight requests are chained to their
+ * target resource for normal handling (as an OPTION request).  Otherwise the 
+ * filter will response to the preflight. Default is true.</li>
+ * </ul></p>
+ * <p>A typical configuration could be:
+ * <pre>
+ * &lt;web-app ...&gt;
+ *     ...
+ *     &lt;filter&gt;
+ *         &lt;filter-name&gt;cross-origin&lt;/filter-name&gt;
+ *         &lt;filter-class&gt;org.eclipse.jetty.servlets.CrossOriginFilter&lt;/filter-class&gt;
+ *     &lt;/filter&gt;
+ *     &lt;filter-mapping&gt;
+ *         &lt;filter-name&gt;cross-origin&lt;/filter-name&gt;
+ *         &lt;url-pattern&gt;/cometd/*&lt;/url-pattern&gt;
+ *     &lt;/filter-mapping&gt;
+ *     ...
+ * &lt;/web-app&gt;
+ * </pre></p>
+ */
+public class CrossOriginFilter implements Filter
+{
+    private static final Logger LOG = Log.getLogger(CrossOriginFilter.class);
+
+    // Request headers
+    private static final String ORIGIN_HEADER = "Origin";
+    public static final String ACCESS_CONTROL_REQUEST_METHOD_HEADER = "Access-Control-Request-Method";
+    public static final String ACCESS_CONTROL_REQUEST_HEADERS_HEADER = "Access-Control-Request-Headers";
+    // Response headers
+    public static final String ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = "Access-Control-Allow-Origin";
+    public static final String ACCESS_CONTROL_ALLOW_METHODS_HEADER = "Access-Control-Allow-Methods";
+    public static final String ACCESS_CONTROL_ALLOW_HEADERS_HEADER = "Access-Control-Allow-Headers";
+    public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age";
+    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials";
+    public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers";
+    // Implementation constants
+    public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins";
+    public static final String ALLOWED_METHODS_PARAM = "allowedMethods";
+    public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders";
+    public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge";
+    public static final String ALLOW_CREDENTIALS_PARAM = "allowCredentials";
+    public static final String EXPOSED_HEADERS_PARAM = "exposedHeaders";
+    public static final String OLD_CHAIN_PREFLIGHT_PARAM = "forwardPreflight";
+    public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight";
+    private static final String ANY_ORIGIN = "*";
+    private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
+
+    private boolean anyOriginAllowed;
+    private List<String> allowedOrigins = new ArrayList<String>();
+    private List<String> allowedMethods = new ArrayList<String>();
+    private List<String> allowedHeaders = new ArrayList<String>();
+    private List<String> exposedHeaders = new ArrayList<String>();
+    private int preflightMaxAge;
+    private boolean allowCredentials;
+    private boolean chainPreflight;
+
+    public void init(FilterConfig config) throws ServletException
+    {
+        String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM);
+        if (allowedOriginsConfig == null)
+            allowedOriginsConfig = "*";
+        String[] allowedOrigins = allowedOriginsConfig.split(",");
+        for (String allowedOrigin : allowedOrigins)
+        {
+            allowedOrigin = allowedOrigin.trim();
+            if (allowedOrigin.length() > 0)
+            {
+                if (ANY_ORIGIN.equals(allowedOrigin))
+                {
+                    anyOriginAllowed = true;
+                    this.allowedOrigins.clear();
+                    break;
+                }
+                else
+                {
+                    this.allowedOrigins.add(allowedOrigin);
+                }
+            }
+        }
+
+        String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);
+        if (allowedMethodsConfig == null)
+            allowedMethodsConfig = "GET,POST,HEAD";
+        allowedMethods.addAll(Arrays.asList(allowedMethodsConfig.split(",")));
+
+        String allowedHeadersConfig = config.getInitParameter(ALLOWED_HEADERS_PARAM);
+        if (allowedHeadersConfig == null)
+            allowedHeadersConfig = "X-Requested-With,Content-Type,Accept,Origin";
+        allowedHeaders.addAll(Arrays.asList(allowedHeadersConfig.split(",")));
+
+        String preflightMaxAgeConfig = config.getInitParameter(PREFLIGHT_MAX_AGE_PARAM);
+        if (preflightMaxAgeConfig == null)
+            preflightMaxAgeConfig = "1800"; // Default is 30 minutes
+        try
+        {
+            preflightMaxAge = Integer.parseInt(preflightMaxAgeConfig);
+        }
+        catch (NumberFormatException x)
+        {
+            LOG.info("Cross-origin filter, could not parse '{}' parameter as integer: {}", PREFLIGHT_MAX_AGE_PARAM, preflightMaxAgeConfig);
+        }
+
+        String allowedCredentialsConfig = config.getInitParameter(ALLOW_CREDENTIALS_PARAM);
+        if (allowedCredentialsConfig == null)
+            allowedCredentialsConfig = "true";
+        allowCredentials = Boolean.parseBoolean(allowedCredentialsConfig);
+
+        String exposedHeadersConfig = config.getInitParameter(EXPOSED_HEADERS_PARAM);
+        if (exposedHeadersConfig == null)
+            exposedHeadersConfig = "";
+        exposedHeaders.addAll(Arrays.asList(exposedHeadersConfig.split(",")));
+
+        String chainPreflightConfig = config.getInitParameter(OLD_CHAIN_PREFLIGHT_PARAM);
+        if (chainPreflightConfig!=null) // TODO remove this
+            LOG.warn("DEPRECATED CONFIGURATION: Use "+CHAIN_PREFLIGHT_PARAM+ " instead of "+OLD_CHAIN_PREFLIGHT_PARAM);
+        else
+            chainPreflightConfig = config.getInitParameter(CHAIN_PREFLIGHT_PARAM);
+        if (chainPreflightConfig == null)
+            chainPreflightConfig = "true";
+        chainPreflight = Boolean.parseBoolean(chainPreflightConfig);
+
+        if (LOG.isDebugEnabled())
+        {
+            LOG.debug("Cross-origin filter configuration: " +
+                    ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
+                    ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
+                    ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
+                    PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
+                    ALLOW_CREDENTIALS_PARAM + " = " + allowedCredentialsConfig + "," +
+                    EXPOSED_HEADERS_PARAM + " = " + exposedHeadersConfig + "," +
+                    CHAIN_PREFLIGHT_PARAM + " = " + chainPreflightConfig
+            );
+        }
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        handle((HttpServletRequest)request, (HttpServletResponse)response, chain);
+    }
+
+    private void handle(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        String origin = request.getHeader(ORIGIN_HEADER);
+        // Is it a cross origin request ?
+        if (origin != null && isEnabled(request))
+        {
+            if (originMatches(origin))
+            {
+                if (isSimpleRequest(request))
+                {
+                    LOG.debug("Cross-origin request to {} is a simple cross-origin request", request.getRequestURI());
+                    handleSimpleResponse(request, response, origin);
+                }
+                else if (isPreflightRequest(request))
+                {
+                    LOG.debug("Cross-origin request to {} is a preflight cross-origin request", request.getRequestURI());
+                    handlePreflightResponse(request, response, origin);
+                    if (chainPreflight)
+                        LOG.debug("Preflight cross-origin request to {} forwarded to application", request.getRequestURI());
+                    else
+                        return;
+                }
+                else
+                {
+                    LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI());
+                    handleSimpleResponse(request, response, origin);
+                }
+            }
+            else
+            {
+                LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed origins " + allowedOrigins);
+            }
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    protected boolean isEnabled(HttpServletRequest request)
+    {
+        // WebSocket clients such as Chrome 5 implement a version of the WebSocket
+        // protocol that does not accept extra response headers on the upgrade response
+        for (Enumeration connections = request.getHeaders("Connection"); connections.hasMoreElements();)
+        {
+            String connection = (String)connections.nextElement();
+            if ("Upgrade".equalsIgnoreCase(connection))
+            {
+                for (Enumeration upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
+                {
+                    String upgrade = (String)upgrades.nextElement();
+                    if ("WebSocket".equalsIgnoreCase(upgrade))
+                        return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean originMatches(String originList)
+    {
+        if (anyOriginAllowed)
+            return true;
+
+        if (originList.trim().length() == 0)
+            return false;
+
+        String[] origins = originList.split(" ");
+        for (String origin : origins)
+        {
+            if (origin.trim().length() == 0)
+                continue;
+
+            for (String allowedOrigin : allowedOrigins)
+            {
+                if (allowedOrigin.contains("*"))
+                {
+                    Matcher matcher = createMatcher(origin,allowedOrigin);
+                    if (matcher.matches())
+                        return true;
+                }
+                else if (allowedOrigin.equals(origin))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private Matcher createMatcher(String origin, String allowedOrigin)
+    {
+        String regex = parseAllowedWildcardOriginToRegex(allowedOrigin);
+        Pattern pattern = Pattern.compile(regex);
+        return pattern.matcher(origin);
+    }
+
+    private String parseAllowedWildcardOriginToRegex(String allowedOrigin)
+    {
+        String regex = allowedOrigin.replace(".","\\.");
+        return regex.replace("*",".*"); // we want to be greedy here to match multiple subdomains, thus we use .*
+    }
+
+    private boolean isSimpleRequest(HttpServletRequest request)
+    {
+        String method = request.getMethod();
+        if (SIMPLE_HTTP_METHODS.contains(method))
+        {
+            // TODO: implement better detection of simple headers
+            // The specification says that for a request to be simple, custom request headers must be simple.
+            // Here for simplicity I just check if there is a Access-Control-Request-Method header,
+            // which is required for preflight requests
+            return request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null;
+        }
+        return false;
+    }
+
+    private boolean isPreflightRequest(HttpServletRequest request)
+    {
+        String method = request.getMethod();
+        if (!"OPTIONS".equalsIgnoreCase(method))
+            return false;
+        if (request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER) == null)
+            return false;
+        return true;
+    }
+
+    private void handleSimpleResponse(HttpServletRequest request, HttpServletResponse response, String origin)
+    {
+        response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
+        if (allowCredentials)
+            response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
+        if (!exposedHeaders.isEmpty())
+            response.setHeader(ACCESS_CONTROL_EXPOSE_HEADERS_HEADER, commify(exposedHeaders));
+    }
+
+    private void handlePreflightResponse(HttpServletRequest request, HttpServletResponse response, String origin)
+    {
+        boolean methodAllowed = isMethodAllowed(request);
+        if (!methodAllowed)
+            return;
+        boolean headersAllowed = areHeadersAllowed(request);
+        if (!headersAllowed)
+            return;
+        response.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, origin);
+        if (allowCredentials)
+            response.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER, "true");
+        if (preflightMaxAge > 0)
+            response.setHeader(ACCESS_CONTROL_MAX_AGE_HEADER, String.valueOf(preflightMaxAge));
+        response.setHeader(ACCESS_CONTROL_ALLOW_METHODS_HEADER, commify(allowedMethods));
+        response.setHeader(ACCESS_CONTROL_ALLOW_HEADERS_HEADER, commify(allowedHeaders));
+    }
+
+    private boolean isMethodAllowed(HttpServletRequest request)
+    {
+        String accessControlRequestMethod = request.getHeader(ACCESS_CONTROL_REQUEST_METHOD_HEADER);
+        LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_METHOD_HEADER, accessControlRequestMethod);
+        boolean result = false;
+        if (accessControlRequestMethod != null)
+            result = allowedMethods.contains(accessControlRequestMethod);
+        LOG.debug("Method {} is" + (result ? "" : " not") + " among allowed methods {}", accessControlRequestMethod, allowedMethods);
+        return result;
+    }
+
+    private boolean areHeadersAllowed(HttpServletRequest request)
+    {
+        String accessControlRequestHeaders = request.getHeader(ACCESS_CONTROL_REQUEST_HEADERS_HEADER);
+        LOG.debug("{} is {}", ACCESS_CONTROL_REQUEST_HEADERS_HEADER, accessControlRequestHeaders);
+        boolean result = true;
+        if (accessControlRequestHeaders != null)
+        {
+            String[] headers = accessControlRequestHeaders.split(",");
+            for (String header : headers)
+            {
+                boolean headerAllowed = false;
+                for (String allowedHeader : allowedHeaders)
+                {
+                    if (header.trim().equalsIgnoreCase(allowedHeader.trim()))
+                    {
+                        headerAllowed = true;
+                        break;
+                    }
+                }
+                if (!headerAllowed)
+                {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        LOG.debug("Headers [{}] are" + (result ? "" : " not") + " among allowed headers {}", accessControlRequestHeaders, allowedHeaders);
+        return result;
+    }
+
+    private String commify(List<String> strings)
+    {
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < strings.size(); ++i)
+        {
+            if (i > 0) builder.append(",");
+            String string = strings.get(i);
+            builder.append(string);
+        }
+        return builder.toString();
+    }
+
+    public void destroy()
+    {
+        anyOriginAllowed = false;
+        allowedOrigins.clear();
+        allowedMethods.clear();
+        allowedHeaders.clear();
+        preflightMaxAge = 0;
+        allowCredentials = false;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/DoSFilter.java b/src/java/org/eclipse/jetty/servlets/DoSFilter.java
new file mode 100644
index 0000000..be20d58
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/DoSFilter.java
@@ -0,0 +1,1151 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.Filter;
+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.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionEvent;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+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.thread.Timeout;
+
+/**
+ * Denial of Service filter
+ * <p/>
+ * <p>
+ * This filter is useful for limiting
+ * exposure to abuse from request flooding, whether malicious, or as a result of
+ * a misconfigured client.
+ * <p>
+ * The filter keeps track of the number of requests from a connection per
+ * second. If a limit is exceeded, the request is either rejected, delayed, or
+ * throttled.
+ * <p>
+ * When a request is throttled, it is placed in a priority queue. Priority is
+ * given first to authenticated users and users with an HttpSession, then
+ * connections which can be identified by their IP addresses. Connections with
+ * no way to identify them are given lowest priority.
+ * <p>
+ * The {@link #extractUserId(ServletRequest request)} function should be
+ * implemented, in order to uniquely identify authenticated users.
+ * <p>
+ * The following init parameters control the behavior of the filter:<dl>
+ * <p/>
+ * <dt>maxRequestsPerSec</dt>
+ * <dd>the maximum number of requests from a connection per
+ * second. Requests in excess of this are first delayed,
+ * then throttled.</dd>
+ * <p/>
+ * <dt>delayMs</dt>
+ * <dd>is the delay given to all requests over the rate limit,
+ * before they are considered at all. -1 means just reject request,
+ * 0 means no delay, otherwise it is the delay.</dd>
+ * <p/>
+ * <dt>maxWaitMs</dt>
+ * <dd>how long to blocking wait for the throttle semaphore.</dd>
+ * <p/>
+ * <dt>throttledRequests</dt>
+ * <dd>is the number of requests over the rate limit able to be
+ * considered at once.</dd>
+ * <p/>
+ * <dt>throttleMs</dt>
+ * <dd>how long to async wait for semaphore.</dd>
+ * <p/>
+ * <dt>maxRequestMs</dt>
+ * <dd>how long to allow this request to run.</dd>
+ * <p/>
+ * <dt>maxIdleTrackerMs</dt>
+ * <dd>how long to keep track of request rates for a connection,
+ * before deciding that the user has gone away, and discarding it</dd>
+ * <p/>
+ * <dt>insertHeaders</dt>
+ * <dd>if true , insert the DoSFilter headers into the response. Defaults to true.</dd>
+ * <p/>
+ * <dt>trackSessions</dt>
+ * <dd>if true, usage rate is tracked by session if a session exists. Defaults to true.</dd>
+ * <p/>
+ * <dt>remotePort</dt>
+ * <dd>if true and session tracking is not used, then rate is tracked by IP+port (effectively connection). Defaults to false.</dd>
+ * <p/>
+ * <dt>ipWhitelist</dt>
+ * <dd>a comma-separated list of IP addresses that will not be rate limited</dd>
+ * <p/>
+ * <dt>managedAttr</dt>
+ * <dd>if set to true, then this servlet is set as a {@link ServletContext} attribute with the
+ * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
+ * manage the configuration of the filter.</dd>
+ * </dl>
+ * </p>
+ */
+public class DoSFilter implements Filter
+{
+    private static final Logger LOG = Log.getLogger(DoSFilter.class);
+
+    private static final String IPv4_GROUP = "(\\d{1,3})";
+    private static final Pattern IPv4_PATTERN = Pattern.compile(IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP);
+    private static final String IPv6_GROUP = "(\\p{XDigit}{1,4})";
+    private static final Pattern IPv6_PATTERN = Pattern.compile(IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP);
+    private static final Pattern CIDR_PATTERN = Pattern.compile("([^/]+)/(\\d+)");
+
+    private static final String __TRACKER = "DoSFilter.Tracker";
+    private static final String __THROTTLED = "DoSFilter.Throttled";
+
+    private static final int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
+    private static final int __DEFAULT_DELAY_MS = 100;
+    private static final int __DEFAULT_THROTTLE = 5;
+    private static final int __DEFAULT_MAX_WAIT_MS = 50;
+    private static final long __DEFAULT_THROTTLE_MS = 30000L;
+    private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L;
+    private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L;
+
+    static final String MANAGED_ATTR_INIT_PARAM = "managedAttr";
+    static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
+    static final String DELAY_MS_INIT_PARAM = "delayMs";
+    static final String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
+    static final String MAX_WAIT_INIT_PARAM = "maxWaitMs";
+    static final String THROTTLE_MS_INIT_PARAM = "throttleMs";
+    static final String MAX_REQUEST_MS_INIT_PARAM = "maxRequestMs";
+    static final String MAX_IDLE_TRACKER_MS_INIT_PARAM = "maxIdleTrackerMs";
+    static final String INSERT_HEADERS_INIT_PARAM = "insertHeaders";
+    static final String TRACK_SESSIONS_INIT_PARAM = "trackSessions";
+    static final String REMOTE_PORT_INIT_PARAM = "remotePort";
+    static final String IP_WHITELIST_INIT_PARAM = "ipWhitelist";
+    static final String ENABLED_INIT_PARAM = "enabled";
+
+    private static final int USER_AUTH = 2;
+    private static final int USER_SESSION = 2;
+    private static final int USER_IP = 1;
+    private static final int USER_UNKNOWN = 0;
+
+    private ServletContext _context;
+    private volatile long _delayMs;
+    private volatile long _throttleMs;
+    private volatile long _maxWaitMs;
+    private volatile long _maxRequestMs;
+    private volatile long _maxIdleTrackerMs;
+    private volatile boolean _insertHeaders;
+    private volatile boolean _trackSessions;
+    private volatile boolean _remotePort;
+    private volatile boolean _enabled;
+    private Semaphore _passes;
+    private volatile int _throttledRequests;
+    private volatile int _maxRequestsPerSec;
+    private Queue<Continuation>[] _queue;
+    private ContinuationListener[] _listeners;
+    private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<String, RateTracker>();
+    private final List<String> _whitelist = new CopyOnWriteArrayList<String>();
+    private final Timeout _requestTimeoutQ = new Timeout();
+    private final Timeout _trackerTimeoutQ = new Timeout();
+    private Thread _timerThread;
+    private volatile boolean _running;
+
+    public void init(FilterConfig filterConfig)
+    {
+        _context = filterConfig.getServletContext();
+
+        _queue = new Queue[getMaxPriority() + 1];
+        _listeners = new ContinuationListener[getMaxPriority() + 1];
+        for (int p = 0; p < _queue.length; p++)
+        {
+            _queue[p] = new ConcurrentLinkedQueue<Continuation>();
+
+            final int priority = p;
+            _listeners[p] = new ContinuationListener()
+            {
+                public void onComplete(Continuation continuation)
+                {
+                }
+
+                public void onTimeout(Continuation continuation)
+                {
+                    _queue[priority].remove(continuation);
+                }
+            };
+        }
+
+        _rateTrackers.clear();
+
+        int maxRequests = __DEFAULT_MAX_REQUESTS_PER_SEC;
+        String parameter = filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM);
+        if (parameter != null)
+            maxRequests = Integer.parseInt(parameter);
+        setMaxRequestsPerSec(maxRequests);
+
+        long delay = __DEFAULT_DELAY_MS;
+        parameter = filterConfig.getInitParameter(DELAY_MS_INIT_PARAM);
+        if (parameter != null)
+            delay = Long.parseLong(parameter);
+        setDelayMs(delay);
+
+        int throttledRequests = __DEFAULT_THROTTLE;
+        parameter = filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM);
+        if (parameter != null)
+            throttledRequests = Integer.parseInt(parameter);
+        setThrottledRequests(throttledRequests);
+
+        long maxWait = __DEFAULT_MAX_WAIT_MS;
+        parameter = filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM);
+        if (parameter != null)
+            maxWait = Long.parseLong(parameter);
+        setMaxWaitMs(maxWait);
+
+        long throttle = __DEFAULT_THROTTLE_MS;
+        parameter = filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM);
+        if (parameter != null)
+            throttle = Long.parseLong(parameter);
+        setThrottleMs(throttle);
+
+        long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
+        parameter = filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM);
+        if (parameter != null)
+            maxRequestMs = Long.parseLong(parameter);
+        setMaxRequestMs(maxRequestMs);
+
+        long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
+        parameter = filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM);
+        if (parameter != null)
+            maxIdleTrackerMs = Long.parseLong(parameter);
+        setMaxIdleTrackerMs(maxIdleTrackerMs);
+
+        String whiteList = "";
+        parameter = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
+        if (parameter != null)
+            whiteList = parameter;
+        setWhitelist(whiteList);
+
+        parameter = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
+        setInsertHeaders(parameter == null || Boolean.parseBoolean(parameter));
+
+        parameter = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
+        setTrackSessions(parameter == null || Boolean.parseBoolean(parameter));
+
+        parameter = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
+        setRemotePort(parameter != null && Boolean.parseBoolean(parameter));
+
+        parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
+        setEnabled(parameter == null || Boolean.parseBoolean(parameter));
+
+        _requestTimeoutQ.setNow();
+        _requestTimeoutQ.setDuration(_maxRequestMs);
+
+        _trackerTimeoutQ.setNow();
+        _trackerTimeoutQ.setDuration(_maxIdleTrackerMs);
+
+        _running = true;
+        _timerThread = (new Thread()
+        {
+            public void run()
+            {
+                try
+                {
+                    while (_running)
+                    {
+                        long now = _requestTimeoutQ.setNow();
+                        _requestTimeoutQ.tick();
+                        _trackerTimeoutQ.setNow(now);
+                        _trackerTimeoutQ.tick();
+                        try
+                        {
+                            Thread.sleep(100);
+                        }
+                        catch (InterruptedException e)
+                        {
+                            LOG.ignore(e);
+                        }
+                    }
+                }
+                finally
+                {
+                    LOG.debug("DoSFilter timer exited");
+                }
+            }
+        });
+        _timerThread.start();
+
+        if (_context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
+            _context.setAttribute(filterConfig.getFilterName(), this);
+    }
+
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
+    {
+        doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain);
+    }
+
+    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException
+    {
+        if (!isEnabled())
+        {
+            filterChain.doFilter(request, response);
+            return;
+        }
+
+        final long now = _requestTimeoutQ.getNow();
+
+        // Look for the rate tracker for this request
+        RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
+
+        if (tracker == null)
+        {
+            // This is the first time we have seen this request.
+
+            // get a rate tracker associated with this request, and record one hit
+            tracker = getRateTracker(request);
+
+            // Calculate the rate and check it is over the allowed limit
+            final boolean overRateLimit = tracker.isRateExceeded(now);
+
+            // pass it through if  we are not currently over the rate limit
+            if (!overRateLimit)
+            {
+                doFilterChain(filterChain, request, response);
+                return;
+            }
+
+            // We are over the limit.
+
+            // So either reject it, delay it or throttle it
+            long delayMs = getDelayMs();
+            boolean insertHeaders = isInsertHeaders();
+            switch ((int)delayMs)
+            {
+                case -1:
+                {
+                    // Reject this request
+                    LOG.warn("DOS ALERT: Request rejected ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    if (insertHeaders)
+                        response.addHeader("DoSFilter", "unavailable");
+                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                    return;
+                }
+                case 0:
+                {
+                    // fall through to throttle code
+                    LOG.warn("DOS ALERT: Request throttled ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    request.setAttribute(__TRACKER, tracker);
+                    break;
+                }
+                default:
+                {
+                    // insert a delay before throttling the request
+                    LOG.warn("DOS ALERT: Request delayed="+delayMs+"ms ip=" + request.getRemoteAddr() + ",session=" + request.getRequestedSessionId() + ",user=" + request.getUserPrincipal());
+                    if (insertHeaders)
+                        response.addHeader("DoSFilter", "delayed");
+                    Continuation continuation = ContinuationSupport.getContinuation(request);
+                    request.setAttribute(__TRACKER, tracker);
+                    if (delayMs > 0)
+                        continuation.setTimeout(delayMs);
+                    continuation.suspend();
+                    return;
+                }
+            }
+        }
+
+        // Throttle the request
+        boolean accepted = false;
+        try
+        {
+            // check if we can afford to accept another request at this time
+            accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
+
+            if (!accepted)
+            {
+                // we were not accepted, so either we suspend to wait,or if we were woken up we insist or we fail
+                final Continuation continuation = ContinuationSupport.getContinuation(request);
+
+                Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
+                long throttleMs = getThrottleMs();
+                if (throttled != Boolean.TRUE && throttleMs > 0)
+                {
+                    int priority = getPriority(request, tracker);
+                    request.setAttribute(__THROTTLED, Boolean.TRUE);
+                    if (isInsertHeaders())
+                        response.addHeader("DoSFilter", "throttled");
+                    if (throttleMs > 0)
+                        continuation.setTimeout(throttleMs);
+                    continuation.suspend();
+
+                    continuation.addContinuationListener(_listeners[priority]);
+                    _queue[priority].add(continuation);
+                    return;
+                }
+                // else were we resumed?
+                else if (request.getAttribute("javax.servlet.resumed") == Boolean.TRUE)
+                {
+                    // we were resumed and somebody stole our pass, so we wait for the next one.
+                    _passes.acquire();
+                    accepted = true;
+                }
+            }
+
+            // if we were accepted (either immediately or after throttle)
+            if (accepted)
+                // call the chain
+                doFilterChain(filterChain, request, response);
+            else
+            {
+                // fail the request
+                if (isInsertHeaders())
+                    response.addHeader("DoSFilter", "unavailable");
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            }
+        }
+        catch (InterruptedException e)
+        {
+            _context.log("DoS", e);
+            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+        }
+        finally
+        {
+            if (accepted)
+            {
+                // wake up the next highest priority request.
+                for (int p = _queue.length; p-- > 0; )
+                {
+                    Continuation continuation = _queue[p].poll();
+                    if (continuation != null && continuation.isSuspended())
+                    {
+                        continuation.resume();
+                        break;
+                    }
+                }
+                _passes.release();
+            }
+        }
+    }
+
+    protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
+    {
+        final Thread thread = Thread.currentThread();
+
+        final Timeout.Task requestTimeout = new Timeout.Task()
+        {
+            public void expired()
+            {
+                closeConnection(request, response, thread);
+            }
+        };
+
+        try
+        {
+            _requestTimeoutQ.schedule(requestTimeout);
+            chain.doFilter(request, response);
+        }
+        finally
+        {
+            requestTimeout.cancel();
+        }
+    }
+
+    /**
+     * Takes drastic measures to return this response and stop this thread.
+     * Due to the way the connection is interrupted, may return mixed up headers.
+     *
+     * @param request  current request
+     * @param response current response, which must be stopped
+     * @param thread   the handling thread
+     */
+    protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
+    {
+        // take drastic measures to return this response and stop this thread.
+        if (!response.isCommitted())
+        {
+            response.setHeader("Connection", "close");
+        }
+        try
+        {
+            try
+            {
+                response.getWriter().close();
+            }
+            catch (IllegalStateException e)
+            {
+                response.getOutputStream().close();
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+
+        // interrupt the handling thread
+        thread.interrupt();
+    }
+
+    /**
+     * Get priority for this request, based on user type
+     *
+     * @param request the current request
+     * @param tracker the rate tracker for this request
+     * @return the priority for this request
+     */
+    protected int getPriority(HttpServletRequest request, RateTracker tracker)
+    {
+        if (extractUserId(request) != null)
+            return USER_AUTH;
+        if (tracker != null)
+            return tracker.getType();
+        return USER_UNKNOWN;
+    }
+
+    /**
+     * @return the maximum priority that we can assign to a request
+     */
+    protected int getMaxPriority()
+    {
+        return USER_AUTH;
+    }
+
+    /**
+     * Return a request rate tracker associated with this connection; keeps
+     * track of this connection's request rate. If this is not the first request
+     * from this connection, return the existing object with the stored stats.
+     * If it is the first request, then create a new request tracker.
+     * <p/>
+     * Assumes that each connection has an identifying characteristic, and goes
+     * through them in order, taking the first that matches: user id (logged
+     * in), session id, client IP address. Unidentifiable connections are lumped
+     * into one.
+     * <p/>
+     * When a session expires, its rate tracker is automatically deleted.
+     *
+     * @param request the current request
+     * @return the request rate tracker for the current connection
+     */
+    public RateTracker getRateTracker(ServletRequest request)
+    {
+        HttpSession session = ((HttpServletRequest)request).getSession(false);
+
+        String loadId = extractUserId(request);
+        final int type;
+        if (loadId != null)
+        {
+            type = USER_AUTH;
+        }
+        else
+        {
+            if (_trackSessions && session != null && !session.isNew())
+            {
+                loadId = session.getId();
+                type = USER_SESSION;
+            }
+            else
+            {
+                loadId = _remotePort ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
+                type = USER_IP;
+            }
+        }
+
+        RateTracker tracker = _rateTrackers.get(loadId);
+
+        if (tracker == null)
+        {
+            boolean allowed = checkWhitelist(_whitelist, request.getRemoteAddr());
+            tracker = allowed ? new FixedRateTracker(loadId, type, _maxRequestsPerSec)
+                    : new RateTracker(loadId, type, _maxRequestsPerSec);
+            RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
+            if (existing != null)
+                tracker = existing;
+
+            if (type == USER_IP)
+            {
+                // USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
+                _trackerTimeoutQ.schedule(tracker);
+            }
+            else if (session != null)
+            {
+                // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
+                session.setAttribute(__TRACKER, tracker);
+            }
+        }
+
+        return tracker;
+    }
+
+    protected boolean checkWhitelist(List<String> whitelist, String candidate)
+    {
+        for (String address : whitelist)
+        {
+            if (address.contains("/"))
+            {
+                if (subnetMatch(address, candidate))
+                    return true;
+            }
+            else
+            {
+                if (address.equals(candidate))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean subnetMatch(String subnetAddress, String address)
+    {
+        Matcher cidrMatcher = CIDR_PATTERN.matcher(subnetAddress);
+        if (!cidrMatcher.matches())
+            return false;
+
+        String subnet = cidrMatcher.group(1);
+        int prefix;
+        try
+        {
+            prefix = Integer.parseInt(cidrMatcher.group(2));
+        }
+        catch (NumberFormatException x)
+        {
+            LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
+            return false;
+        }
+
+        byte[] subnetBytes = addressToBytes(subnet);
+        if (subnetBytes == null)
+        {
+            LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
+            return false;
+        }
+        byte[] addressBytes = addressToBytes(address);
+        if (addressBytes == null)
+        {
+            LOG.info("Ignoring malformed remote address {}", address);
+            return false;
+        }
+
+        // Comparing IPv4 with IPv6 ?
+        int length = subnetBytes.length;
+        if (length != addressBytes.length)
+            return false;
+
+        byte[] mask = prefixToBytes(prefix, length);
+
+        for (int i = 0; i < length; ++i)
+        {
+            if ((subnetBytes[i] & mask[i]) != (addressBytes[i] & mask[i]))
+                return false;
+        }
+
+        return true;
+    }
+
+    private byte[] addressToBytes(String address)
+    {
+        Matcher ipv4Matcher = IPv4_PATTERN.matcher(address);
+        if (ipv4Matcher.matches())
+        {
+            byte[] result = new byte[4];
+            for (int i = 0; i < result.length; ++i)
+                result[i] = Integer.valueOf(ipv4Matcher.group(i + 1)).byteValue();
+            return result;
+        }
+        else
+        {
+            Matcher ipv6Matcher = IPv6_PATTERN.matcher(address);
+            if (ipv6Matcher.matches())
+            {
+                byte[] result = new byte[16];
+                for (int i = 0; i < result.length; i += 2)
+                {
+                    int word = Integer.valueOf(ipv6Matcher.group(i / 2 + 1), 16);
+                    result[i] = (byte)((word & 0xFF00) >>> 8);
+                    result[i + 1] = (byte)(word & 0xFF);
+                }
+                return result;
+            }
+        }
+        return null;
+    }
+
+    private byte[] prefixToBytes(int prefix, int length)
+    {
+        byte[] result = new byte[length];
+        int index = 0;
+        while (prefix / 8 > 0)
+        {
+            result[index] = -1;
+            prefix -= 8;
+            ++index;
+        }
+        // Sets the _prefix_ most significant bits to 1
+        result[index] = (byte)~((1 << (8 - prefix)) - 1);
+        return result;
+    }
+
+    public void destroy()
+    {
+        LOG.debug("Destroy {}",this);
+        _running = false;
+        _timerThread.interrupt();
+        _requestTimeoutQ.cancelAll();
+        _trackerTimeoutQ.cancelAll();
+        _rateTrackers.clear();
+        _whitelist.clear();
+    }
+
+    /**
+     * Returns the user id, used to track this connection.
+     * This SHOULD be overridden by subclasses.
+     *
+     * @param request the current request
+     * @return a unique user id, if logged in; otherwise null.
+     */
+    protected String extractUserId(ServletRequest request)
+    {
+        return null;
+    }
+
+    /**
+     * Get maximum number of requests from a connection per
+     * second. Requests in excess of this are first delayed,
+     * then throttled.
+     *
+     * @return maximum number of requests
+     */
+    public int getMaxRequestsPerSec()
+    {
+        return _maxRequestsPerSec;
+    }
+
+    /**
+     * Get maximum number of requests from a connection per
+     * second. Requests in excess of this are first delayed,
+     * then throttled.
+     *
+     * @param value maximum number of requests
+     */
+    public void setMaxRequestsPerSec(int value)
+    {
+        _maxRequestsPerSec = value;
+    }
+
+    /**
+     * Get delay (in milliseconds) that is applied to all requests
+     * over the rate limit, before they are considered at all.
+     */
+    public long getDelayMs()
+    {
+        return _delayMs;
+    }
+
+    /**
+     * Set delay (in milliseconds) that is applied to all requests
+     * over the rate limit, before they are considered at all.
+     *
+     * @param value delay (in milliseconds), 0 - no delay, -1 - reject request
+     */
+    public void setDelayMs(long value)
+    {
+        _delayMs = value;
+    }
+
+    /**
+     * Get maximum amount of time (in milliseconds) the filter will
+     * blocking wait for the throttle semaphore.
+     *
+     * @return maximum wait time
+     */
+    public long getMaxWaitMs()
+    {
+        return _maxWaitMs;
+    }
+
+    /**
+     * Set maximum amount of time (in milliseconds) the filter will
+     * blocking wait for the throttle semaphore.
+     *
+     * @param value maximum wait time
+     */
+    public void setMaxWaitMs(long value)
+    {
+        _maxWaitMs = value;
+    }
+
+    /**
+     * Get number of requests over the rate limit able to be
+     * considered at once.
+     *
+     * @return number of requests
+     */
+    public int getThrottledRequests()
+    {
+        return _throttledRequests;
+    }
+
+    /**
+     * Set number of requests over the rate limit able to be
+     * considered at once.
+     *
+     * @param value number of requests
+     */
+    public void setThrottledRequests(int value)
+    {
+        int permits = _passes == null ? 0 : _passes.availablePermits();
+        _passes = new Semaphore((value - _throttledRequests + permits), true);
+        _throttledRequests = value;
+    }
+
+    /**
+     * Get amount of time (in milliseconds) to async wait for semaphore.
+     *
+     * @return wait time
+     */
+    public long getThrottleMs()
+    {
+        return _throttleMs;
+    }
+
+    /**
+     * Set amount of time (in milliseconds) to async wait for semaphore.
+     *
+     * @param value wait time
+     */
+    public void setThrottleMs(long value)
+    {
+        _throttleMs = value;
+    }
+
+    /**
+     * Get maximum amount of time (in milliseconds) to allow
+     * the request to process.
+     *
+     * @return maximum processing time
+     */
+    public long getMaxRequestMs()
+    {
+        return _maxRequestMs;
+    }
+
+    /**
+     * Set maximum amount of time (in milliseconds) to allow
+     * the request to process.
+     *
+     * @param value maximum processing time
+     */
+    public void setMaxRequestMs(long value)
+    {
+        _maxRequestMs = value;
+    }
+
+    /**
+     * Get maximum amount of time (in milliseconds) to keep track
+     * of request rates for a connection, before deciding that
+     * the user has gone away, and discarding it.
+     *
+     * @return maximum tracking time
+     */
+    public long getMaxIdleTrackerMs()
+    {
+        return _maxIdleTrackerMs;
+    }
+
+    /**
+     * Set maximum amount of time (in milliseconds) to keep track
+     * of request rates for a connection, before deciding that
+     * the user has gone away, and discarding it.
+     *
+     * @param value maximum tracking time
+     */
+    public void setMaxIdleTrackerMs(long value)
+    {
+        _maxIdleTrackerMs = value;
+    }
+
+    /**
+     * Check flag to insert the DoSFilter headers into the response.
+     *
+     * @return value of the flag
+     */
+    public boolean isInsertHeaders()
+    {
+        return _insertHeaders;
+    }
+
+    /**
+     * Set flag to insert the DoSFilter headers into the response.
+     *
+     * @param value value of the flag
+     */
+    public void setInsertHeaders(boolean value)
+    {
+        _insertHeaders = value;
+    }
+
+    /**
+     * Get flag to have usage rate tracked by session if a session exists.
+     *
+     * @return value of the flag
+     */
+    public boolean isTrackSessions()
+    {
+        return _trackSessions;
+    }
+
+    /**
+     * Set flag to have usage rate tracked by session if a session exists.
+     *
+     * @param value value of the flag
+     */
+    public void setTrackSessions(boolean value)
+    {
+        _trackSessions = value;
+    }
+
+    /**
+     * Get flag to have usage rate tracked by IP+port (effectively connection)
+     * if session tracking is not used.
+     *
+     * @return value of the flag
+     */
+    public boolean isRemotePort()
+    {
+        return _remotePort;
+    }
+
+    /**
+     * Set flag to have usage rate tracked by IP+port (effectively connection)
+     * if session tracking is not used.
+     *
+     * @param value value of the flag
+     */
+    public void setRemotePort(boolean value)
+    {
+        _remotePort = value;
+    }
+
+    /**
+     * @return whether this filter is enabled
+     */
+    public boolean isEnabled()
+    {
+        return _enabled;
+    }
+
+    /**
+     * @param enabled whether this filter is enabled
+     */
+    public void setEnabled(boolean enabled)
+    {
+        _enabled = enabled;
+    }
+
+    /**
+     * Get a list of IP addresses that will not be rate limited.
+     *
+     * @return comma-separated whitelist
+     */
+    public String getWhitelist()
+    {
+        StringBuilder result = new StringBuilder();
+        for (Iterator<String> iterator = _whitelist.iterator(); iterator.hasNext();)
+        {
+            String address = iterator.next();
+            result.append(address);
+            if (iterator.hasNext())
+                result.append(",");
+        }
+        return result.toString();
+    }
+
+    /**
+     * Set a list of IP addresses that will not be rate limited.
+     *
+     * @param value comma-separated whitelist
+     */
+    public void setWhitelist(String value)
+    {
+        List<String> result = new ArrayList<String>();
+        for (String address : value.split(","))
+            addWhitelistAddress(result, address);
+        _whitelist.clear();
+        _whitelist.addAll(result);
+        LOG.debug("Whitelisted IP addresses: {}", result);
+    }
+
+    public void clearWhitelist()
+    {
+        _whitelist.clear();
+    }
+
+    public boolean addWhitelistAddress(String address)
+    {
+        return addWhitelistAddress(_whitelist, address);
+    }
+
+    private boolean addWhitelistAddress(List<String> list, String address)
+    {
+        address = address.trim();
+        return address.length() > 0 && list.add(address);
+    }
+
+    public boolean removeWhitelistAddress(String address)
+    {
+        return _whitelist.remove(address);
+    }
+
+    /**
+     * A RateTracker is associated with a connection, and stores request rate
+     * data.
+     */
+    class RateTracker extends Timeout.Task implements HttpSessionBindingListener, HttpSessionActivationListener, Serializable
+    {
+        private static final long serialVersionUID = 3534663738034577872L;
+
+        transient protected final String _id;
+        transient protected final int _type;
+        transient protected final long[] _timestamps;
+        transient protected int _next;
+
+        public RateTracker(String id, int type, int maxRequestsPerSecond)
+        {
+            _id = id;
+            _type = type;
+            _timestamps = new long[maxRequestsPerSecond];
+            _next = 0;
+        }
+
+        /**
+         * @return the current calculated request rate over the last second
+         */
+        public boolean isRateExceeded(long now)
+        {
+            final long last;
+            synchronized (this)
+            {
+                last = _timestamps[_next];
+                _timestamps[_next] = now;
+                _next = (_next + 1) % _timestamps.length;
+            }
+
+            return last != 0 && (now - last) < 1000L;
+        }
+
+        public String getId()
+        {
+            return _id;
+        }
+
+        public int getType()
+        {
+            return _type;
+        }
+
+        public void valueBound(HttpSessionBindingEvent event)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Value bound: {}", getId());
+        }
+
+        public void valueUnbound(HttpSessionBindingEvent event)
+        {
+            //take the tracker out of the list of trackers
+            _rateTrackers.remove(_id);
+            if (LOG.isDebugEnabled())
+                LOG.debug("Tracker removed: {}", getId());
+        }
+
+        public void sessionWillPassivate(HttpSessionEvent se)
+        {
+            //take the tracker of the list of trackers (if its still there)
+            //and ensure that we take ourselves out of the session so we are not saved
+            _rateTrackers.remove(_id);
+            se.getSession().removeAttribute(__TRACKER);
+            if (LOG.isDebugEnabled()) LOG.debug("Value removed: {}", getId());
+        }
+
+        public void sessionDidActivate(HttpSessionEvent se)
+        {
+            LOG.warn("Unexpected session activation");
+        }
+
+        public void expired()
+        {
+            long now = _trackerTimeoutQ.getNow();
+            int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
+            long last = _timestamps[latestIndex];
+            boolean hasRecentRequest = last != 0 && (now - last) < 1000L;
+
+            if (hasRecentRequest)
+                reschedule();
+            else
+                _rateTrackers.remove(_id);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "RateTracker/" + _id + "/" + _type;
+        }
+    }
+
+    class FixedRateTracker extends RateTracker
+    {
+        public FixedRateTracker(String id, int type, int numRecentRequestsTracked)
+        {
+            super(id, type, numRecentRequestsTracked);
+        }
+
+        @Override
+        public boolean isRateExceeded(long now)
+        {
+            // rate limit is never exceeded, but we keep track of the request timestamps
+            // so that we know whether there was recent activity on this tracker
+            // and whether it should be expired
+            synchronized (this)
+            {
+                _timestamps[_next] = now;
+                _next = (_next + 1) % _timestamps.length;
+            }
+
+            return false;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "Fixed" + super.toString();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/GzipFilter.java b/src/java/org/eclipse/jetty/servlets/GzipFilter.java
new file mode 100644
index 0000000..84303d5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/GzipFilter.java
@@ -0,0 +1,590 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPOutputStream;
+
+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.HttpMethods;
+import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** GZIP Filter
+ * This filter will gzip or deflate the content of a response if: <ul>
+ * <li>The filter is mapped to a matching path</li>
+ * <li>accept-encoding header is set to either gzip, deflate or a combination of those</li>
+ * <li>The response status code is >=200 and <300
+ * <li>The content length is unknown or more than the <code>minGzipSize</code> initParameter or the minGzipSize is 0(default)</li>
+ * <li>The content-type is in the comma separated list of mimeTypes set in the <code>mimeTypes</code> initParameter or
+ * if no mimeTypes are defined the content-type is not "application/gzip"</li>
+ * <li>No content-encoding is specified by the resource</li>
+ * </ul>
+ * 
+ * <p>
+ * If both gzip and deflate are specified in the accept-encoding header, then gzip will be used.
+ * </p>
+ * <p>
+ * Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and
+ * CPU cycles. If this filter is mapped for static content, then use of efficient direct NIO may be 
+ * prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is 
+ * advised instead.
+ * </p>
+ * <p>
+ * This filter extends {@link UserAgentFilter} and if the the initParameter <code>excludedAgents</code> 
+ * is set to a comma separated list of user agents, then these agents will be excluded from gzip content.
+ * </p>
+ * <p>Init Parameters:</p>
+ * <PRE>
+ * bufferSize                 The output buffer size. Defaults to 8192. Be careful as values <= 0 will lead to an 
+ *                            {@link IllegalArgumentException}. 
+ *                            See: {@link java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, int)}
+ *                            and: {@link java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, Deflater, int)}
+ *                      
+ * minGzipSize                Content will only be compressed if content length is either unknown or greater
+ *                            than <code>minGzipSize</code>.
+ *                      
+ * deflateCompressionLevel    The compression level used for deflate compression. (0-9).
+ *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
+ *                            
+ * deflateNoWrap              The noWrap setting for deflate compression. Defaults to true. (true/false)
+ *                            See: {@link java.util.zip.Deflater#Deflater(int, boolean)}
+ *
+ * methods                    Comma separated list of HTTP methods to compress. If not set, only GET requests are compressed.
+ * 
+ * mimeTypes                  Comma separated list of mime types to compress. See description above.
+ * 
+ * excludedAgents             Comma separated list of user agents to exclude from compression. Does a 
+ *                            {@link String#contains(CharSequence)} to check if the excluded agent occurs
+ *                            in the user-agent header. If it does -> no compression
+ *                            
+ * excludeAgentPatterns       Same as excludedAgents, but accepts regex patterns for more complex matching.
+ * 
+ * excludePaths               Comma separated list of paths to exclude from compression. 
+ *                            Does a {@link String#startsWith(String)} comparison to check if the path matches.
+ *                            If it does match -> no compression. To match subpaths use <code>excludePathPatterns</code>
+ *                            instead.
+ * 
+ * excludePathPatterns        Same as excludePath, but accepts regex patterns for more complex matching.
+ * 
+ * vary                       Set to the value of the Vary header sent with responses that could be compressed.  By default it is 
+ *                            set to 'Vary: Accept-Encoding, User-Agent' since IE6 is excluded by default from the excludedAgents. 
+ *                            If user-agents are not to be excluded, then this can be set to 'Vary: Accept-Encoding'.  Note also 
+ *                            that shared caches may cache copies of a resource that is varied by User-Agent - one per variation of 
+ *                            the User-Agent, unless the cache does some normalization of the UA string.
+ * </PRE>
+ */
+public class GzipFilter extends UserAgentFilter
+{
+    private static final Logger LOG = Log.getLogger(GzipFilter.class);
+    public final static String GZIP="gzip";
+    public final static String ETAG_GZIP="--gzip\"";
+    public final static String DEFLATE="deflate";
+    public final static String ETAG_DEFLATE="--deflate\"";
+    public final static String ETAG="o.e.j.s.GzipFilter.ETag";
+
+    protected ServletContext _context;
+    protected Set<String> _mimeTypes;
+    protected int _bufferSize=8192;
+    protected int _minGzipSize=256;
+    protected int _deflateCompressionLevel=Deflater.DEFAULT_COMPRESSION;
+    protected boolean _deflateNoWrap = true;
+
+    protected final Set<String> _methods=new HashSet<String>();
+    protected Set<String> _excludedAgents;
+    protected Set<Pattern> _excludedAgentPatterns;
+    protected Set<String> _excludedPaths;
+    protected Set<Pattern> _excludedPathPatterns;
+    protected String _vary="Accept-Encoding, User-Agent";
+    
+    private static final int STATE_SEPARATOR = 0;
+    private static final int STATE_Q = 1;
+    private static final int STATE_QVALUE = 2;
+    private static final int STATE_DEFAULT = 3;
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#init(javax.servlet.FilterConfig)
+     */
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        super.init(filterConfig);
+        
+        _context=filterConfig.getServletContext();
+        
+        String tmp=filterConfig.getInitParameter("bufferSize");
+        if (tmp!=null)
+            _bufferSize=Integer.parseInt(tmp);
+
+        tmp=filterConfig.getInitParameter("minGzipSize");
+        if (tmp!=null)
+            _minGzipSize=Integer.parseInt(tmp);
+        
+        tmp=filterConfig.getInitParameter("deflateCompressionLevel");
+        if (tmp!=null)
+            _deflateCompressionLevel=Integer.parseInt(tmp);
+        
+        tmp=filterConfig.getInitParameter("deflateNoWrap");
+        if (tmp!=null)
+            _deflateNoWrap=Boolean.parseBoolean(tmp);
+        
+        tmp=filterConfig.getInitParameter("methods");
+        if (tmp!=null)
+        {
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _methods.add(tok.nextToken().trim().toUpperCase());
+        }
+        else
+            _methods.add(HttpMethods.GET);
+        
+        tmp=filterConfig.getInitParameter("mimeTypes");
+        if (tmp!=null)
+        {
+            _mimeTypes=new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _mimeTypes.add(tok.nextToken());
+        }
+        tmp=filterConfig.getInitParameter("excludedAgents");
+        if (tmp!=null)
+        {
+            _excludedAgents=new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+               _excludedAgents.add(tok.nextToken());
+        }
+        
+                tmp=filterConfig.getInitParameter("excludeAgentPatterns");
+        if (tmp!=null)
+        {
+            _excludedAgentPatterns=new HashSet<Pattern>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedAgentPatterns.add(Pattern.compile(tok.nextToken()));            
+        }        
+        
+        tmp=filterConfig.getInitParameter("excludePaths");
+        if (tmp!=null)
+        {
+            _excludedPaths=new HashSet<String>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedPaths.add(tok.nextToken());            
+        }
+        
+        tmp=filterConfig.getInitParameter("excludePathPatterns");
+        if (tmp!=null)
+        {
+            _excludedPathPatterns=new HashSet<Pattern>();
+            StringTokenizer tok = new StringTokenizer(tmp,",",false);
+            while (tok.hasMoreTokens())
+                _excludedPathPatterns.add(Pattern.compile(tok.nextToken()));            
+        }       
+        
+        tmp=filterConfig.getInitParameter("vary");
+        if (tmp!=null)
+            _vary=tmp;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#destroy()
+     */
+    @Override
+    public void destroy()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.UserAgentFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    @Override
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
+        throws IOException, ServletException
+    {
+        HttpServletRequest request=(HttpServletRequest)req;
+        HttpServletResponse response=(HttpServletResponse)res;
+
+        // If not a supported method or it is an Excluded URI - no Vary because no matter what client, this URI is always excluded
+        String requestURI = request.getRequestURI();
+        if (!_methods.contains(request.getMethod()) || isExcludedPath(requestURI))
+        {
+            super.doFilter(request,response,chain);
+            return;
+        }
+        
+        // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
+        if (_mimeTypes!=null && _mimeTypes.size()>0)
+        {
+            String mimeType = _context.getMimeType(request.getRequestURI());
+            
+            if (mimeType!=null && !_mimeTypes.contains(mimeType))
+            {
+                // handle normally without setting vary header
+                super.doFilter(request,response,chain);
+                return;
+            }
+        }
+        
+        // Excluded User-Agents
+        String ua = getUserAgent(request);
+        boolean ua_excluded=ua!=null&&isExcludedAgent(ua);
+        
+        // Acceptable compression type
+        String compressionType = ua_excluded?null:selectCompression(request.getHeader("accept-encoding"));
+        
+        // Special handling for etags
+        String etag = request.getHeader("If-None-Match"); 
+        if (etag!=null)
+        {
+            int dd=etag.indexOf("--");
+            if (dd>0)
+                request.setAttribute(ETAG,etag.substring(0,dd)+(etag.endsWith("\"")?"\"":""));
+        }
+
+        CompressedResponseWrapper wrappedResponse = createWrappedResponse(request,response,compressionType);
+
+        boolean exceptional=true;
+        try
+        {
+            super.doFilter(request,wrappedResponse,chain);
+            exceptional=false;
+        }
+        finally
+        {
+            Continuation continuation = ContinuationSupport.getContinuation(request);
+            if (continuation.isSuspended() && continuation.isResponseWrapped())   
+            {
+                continuation.addContinuationListener(new ContinuationListenerWaitingForWrappedResponseToFinish(wrappedResponse));
+            }
+            else if (exceptional && !response.isCommitted())
+            {
+                wrappedResponse.resetBuffer();
+                wrappedResponse.noCompression();
+            }
+            else
+                wrappedResponse.finish();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private String selectCompression(String encodingHeader)
+    {
+        // TODO, this could be a little more robust.
+        // prefer gzip over deflate
+        String compression = null;
+        if (encodingHeader!=null)
+        {
+            
+            String[] encodings = getEncodings(encodingHeader);
+            if (encodings != null)
+            {
+                for (int i=0; i< encodings.length; i++)
+                {
+                    if (encodings[i].toLowerCase(Locale.ENGLISH).contains(GZIP))
+                    {
+                        if (isEncodingAcceptable(encodings[i]))
+                        {
+                            compression = GZIP;
+                            break; //prefer Gzip over deflate
+                        }
+                    }
+
+                    if (encodings[i].toLowerCase(Locale.ENGLISH).contains(DEFLATE))
+                    {
+                        if (isEncodingAcceptable(encodings[i]))
+                        {
+                            compression = DEFLATE; //Keep checking in case gzip is acceptable
+                        }
+                    }
+                }
+            }
+        }
+        return compression;
+    }
+    
+    
+    private String[] getEncodings (String encodingHeader)
+    {
+        if (encodingHeader == null)
+            return null;
+        return encodingHeader.split(",");
+    }
+    
+    private boolean isEncodingAcceptable(String encoding)
+    {    
+        int state = STATE_DEFAULT;
+        int qvalueIdx = -1;
+        for (int i=0;i<encoding.length();i++)
+        {
+            char c = encoding.charAt(i);
+            switch (state)
+            {
+                case STATE_DEFAULT:
+                {
+                    if (';' == c)
+                        state = STATE_SEPARATOR;
+                    break;
+                }
+                case STATE_SEPARATOR:
+                {
+                    if ('q' == c || 'Q' == c)
+                        state = STATE_Q;
+                    break;
+                }
+                case STATE_Q:
+                {
+                    if ('=' == c)
+                        state = STATE_QVALUE;
+                    break;
+                }
+                case STATE_QVALUE:
+                {
+                    if (qvalueIdx < 0 && '0' == c || '1' == c)
+                        qvalueIdx = i;
+                    break;
+                }
+            }
+        }
+        
+        if (qvalueIdx < 0)
+            return true;
+               
+        if ("0".equals(encoding.substring(qvalueIdx).trim()))
+            return false;
+        return true;
+    }
+    
+    
+    protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
+    {
+        CompressedResponseWrapper wrappedResponse = null;
+        if (compressionType==null)
+        {
+            wrappedResponse = new CompressedResponseWrapper(request,response)
+            {
+                @Override
+                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+                {
+                    return new AbstractCompressedStream(null,request,this,_vary)
+                    {
+                        @Override
+                        protected DeflaterOutputStream createStream() throws IOException
+                        {
+                            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
+                        {
+                            return new GZIPOutputStream(_response.getOutputStream(),_bufferSize)
+                            {
+                                /**
+                                 * Work around a bug in the jvm GzipOutputStream whereby it is not
+                                 * thread safe when thread A calls finish, but thread B is writing
+                                 * @see java.util.zip.GZIPOutputStream#finish()
+                                 */
+                                @Override
+                                public synchronized void finish() throws IOException
+                                {
+                                    super.finish();
+                                }
+                                
+                                /**
+                                 * Work around a bug in the jvm GzipOutputStream whereby it is not
+                                 * thread safe when thread A calls close(), but thread B is writing
+                                 * @see java.util.zip.GZIPOutputStream#close()
+                                 */
+                                @Override
+                                public synchronized void close() throws IOException
+                                {
+                                    super.close();
+                                }           
+                            };
+                        }
+                    };
+                }
+            };
+        }
+        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
+                        {
+                            return new DeflaterOutputStream(_response.getOutputStream(),new Deflater(_deflateCompressionLevel,_deflateNoWrap));
+                        }
+                    };
+                }
+            };
+        } 
+        else
+        {
+            throw new IllegalStateException(compressionType + " not supported");
+        }
+        configureWrappedResponse(wrappedResponse);
+        return wrappedResponse;
+    }
+
+    protected void configureWrappedResponse(CompressedResponseWrapper wrappedResponse)
+    {
+        wrappedResponse.setMimeTypes(_mimeTypes);
+        wrappedResponse.setBufferSize(_bufferSize);
+        wrappedResponse.setMinCompressSize(_minGzipSize);
+    }
+     
+    private class ContinuationListenerWaitingForWrappedResponseToFinish implements ContinuationListener
+    {    
+        private CompressedResponseWrapper wrappedResponse;
+
+        public ContinuationListenerWaitingForWrappedResponseToFinish(CompressedResponseWrapper wrappedResponse)
+        {
+            this.wrappedResponse = wrappedResponse;
+        }
+
+        public void onComplete(Continuation continuation)
+        {
+            try
+            {
+                wrappedResponse.finish();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        public void onTimeout(Continuation continuation)
+        {
+        }
+    }
+    
+    /**
+     * Checks to see if the userAgent is excluded
+     * 
+     * @param ua
+     *            the user agent
+     * @return boolean true if excluded
+     */
+    private boolean isExcludedAgent(String ua)
+    {
+        if (ua == null)
+            return false;
+
+        if (_excludedAgents != null)
+        {
+            if (_excludedAgents.contains(ua))
+            {
+                return true;
+            }
+        }
+        if (_excludedAgentPatterns != null)
+        {
+            for (Pattern pattern : _excludedAgentPatterns)
+            {
+                if (pattern.matcher(ua).matches())
+                {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks to see if the path is excluded
+     * 
+     * @param requestURI
+     *            the request uri
+     * @return boolean true if excluded
+     */
+    private boolean isExcludedPath(String requestURI)
+    {
+        if (requestURI == null)
+            return false;
+        if (_excludedPaths != null)
+        {
+            for (String excludedPath : _excludedPaths)
+            {
+                if (requestURI.startsWith(excludedPath))
+                {
+                    return true;
+                }
+            }
+        }
+        if (_excludedPathPatterns != null)
+        {
+            for (Pattern pattern : _excludedPathPatterns)
+            {
+                if (pattern.matcher(requestURI).matches())
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java b/src/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
new file mode 100644
index 0000000..04aebdc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/IncludableGzipFilter.java
@@ -0,0 +1,171 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.gzip.CompressedResponseWrapper;
+import org.eclipse.jetty.http.gzip.AbstractCompressedStream;
+import org.eclipse.jetty.io.UncheckedPrintWriter;
+
+/* ------------------------------------------------------------ */
+/** Includable GZip Filter.
+ * This extension to the {@link GzipFilter} that uses Jetty features to allow
+ * headers to be set during calls to 
+ * {@link javax.servlet.RequestDispatcher#include(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
+ * This allows the gzip filter to function correct during includes and to make a decision to gzip or not
+ * at the time the buffer fills and on the basis of all response headers.
+ * 
+ * If the init parameter "uncheckedPrintWriter" is set to "true", then the PrintWriter used by
+ * the wrapped getWriter will be {@link UncheckedPrintWriter}.
+ *
+ */
+public class IncludableGzipFilter extends GzipFilter
+{
+    boolean _uncheckedPrintWriter=false;
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        super.init(filterConfig);
+        
+        String tmp=filterConfig.getInitParameter("uncheckedPrintWriter");
+        if (tmp!=null)
+            _uncheckedPrintWriter=Boolean.valueOf(tmp).booleanValue();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.servlets.GzipFilter#createWrappedResponse(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.String)
+     */
+    @Override
+    protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
+    {
+        CompressedResponseWrapper wrappedResponse = null;
+        if (compressionType==null)
+        {
+            wrappedResponse = new IncludableResponseWrapper(request,response)
+            {
+                @Override
+                protected AbstractCompressedStream newCompressedStream(HttpServletRequest request,HttpServletResponse response) throws IOException
+                {
+                    return new AbstractCompressedStream(null,request,this,_vary)
+                    {
+                        @Override
+                        protected DeflaterOutputStream createStream() throws IOException
+                        {
+                            return null;
+                        }
+                    };
+                }
+            };
+        }
+        else if (compressionType.equals(GZIP))
+        {
+            wrappedResponse = new IncludableResponseWrapper(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
+                        {
+                            return new GZIPOutputStream(_response.getOutputStream(),_bufferSize);
+                        }
+                    };
+                }
+            };
+        }
+        else if (compressionType.equals(DEFLATE))
+        {
+            wrappedResponse = new IncludableResponseWrapper(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
+                        {
+                            return new DeflaterOutputStream(_response.getOutputStream(),new Deflater(_deflateCompressionLevel, _deflateNoWrap));
+                        }
+                    };
+                }
+            };
+        }
+        else
+        {
+            throw new IllegalStateException(compressionType + " not supported");
+        }
+        configureWrappedResponse(wrappedResponse);
+        return wrappedResponse;
+    }
+
+
+    // Extend CompressedResponseWrapper to be able to set headers during include and to create unchecked printwriters
+    private abstract class IncludableResponseWrapper extends CompressedResponseWrapper
+    {
+        public IncludableResponseWrapper(HttpServletRequest request, HttpServletResponse response)
+        {
+            super(request,response);
+        }
+
+        @Override
+        public void setHeader(String name,String value)
+        {
+            super.setHeader(name,value);
+            HttpServletResponse response = (HttpServletResponse)getResponse();
+            if (!response.containsHeader(name))
+                response.setHeader("org.eclipse.jetty.server.include."+name,value);
+        }
+
+        @Override
+        public void addHeader(String name, String value)
+        {
+            super.addHeader(name, value);
+            HttpServletResponse response = (HttpServletResponse)getResponse();
+            if (!response.containsHeader(name))
+                setHeader(name,value);
+        }
+        
+        @Override
+        protected PrintWriter newWriter(OutputStream out, String encoding) throws UnsupportedEncodingException
+        {
+            if (_uncheckedPrintWriter)
+                return encoding == null?new UncheckedPrintWriter(out):new UncheckedPrintWriter(new OutputStreamWriter(out,encoding));
+            return super.newWriter(out,encoding);
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/src/java/org/eclipse/jetty/servlets/MultiPartFilter.java
new file mode 100644
index 0000000..980c1af
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/MultiPartFilter.java
@@ -0,0 +1,372 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.MultiMap;
+import org.eclipse.jetty.util.MultiPartInputStream;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Multipart Form Data Filter.
+ * <p>
+ * This class decodes the multipart/form-data stream sent by a HTML form that uses a file input
+ * item.  Any files sent are stored to a temporary file and a File object added to the request 
+ * as an attribute.  All other values are made available via the normal getParameter API and
+ * the setCharacterEncoding mechanism is respected when converting bytes to Strings.
+ * <p>
+ * If the init parameter "delete" is set to "true", any files created will be deleted when the
+ * current request returns.
+ * <p>
+ * The init parameter maxFormKeys sets the maximum number of keys that may be present in a 
+ * form (default set by system property org.eclipse.jetty.server.Request.maxFormKeys or 1000) to protect 
+ * against DOS attacks by bad hash keys. 
+ * <p>
+ * The init parameter deleteFiles controls if uploaded files are automatically deleted after the request
+ * completes.
+ * 
+ * Use init parameter "maxFileSize" to set the max size file that can be uploaded.
+ * 
+ * Use init parameter "maxRequestSize" to limit the size of the multipart request.
+ * 
+ */
+public class MultiPartFilter implements Filter
+{
+    private static final Logger LOG = Log.getLogger(MultiPartFilter.class);
+    public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType";
+    private final static String MULTIPART = "org.eclipse.jetty.servlet.MultiPartFile.multiPartInputStream";
+    private File tempdir;
+    private boolean _deleteFiles;
+    private ServletContext _context;
+    private int _fileOutputBuffer = 0;
+    private long _maxFileSize = -1L;
+    private long _maxRequestSize = -1L;
+    private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        tempdir=(File)filterConfig.getServletContext().getAttribute("javax.servlet.context.tempdir");
+        _deleteFiles="true".equals(filterConfig.getInitParameter("deleteFiles"));
+        String fileOutputBuffer = filterConfig.getInitParameter("fileOutputBuffer");
+        if(fileOutputBuffer!=null)
+            _fileOutputBuffer = Integer.parseInt(fileOutputBuffer);
+        String maxFileSize = filterConfig.getInitParameter("maxFileSize");
+        if (maxFileSize != null)
+            _maxFileSize = Long.parseLong(maxFileSize.trim());
+        String maxRequestSize = filterConfig.getInitParameter("maxRequestSize");
+        if (maxRequestSize != null)
+            _maxRequestSize = Long.parseLong(maxRequestSize.trim());
+        
+        _context=filterConfig.getServletContext();
+        String mfks = filterConfig.getInitParameter("maxFormKeys");
+        if (mfks!=null)
+            _maxFormKeys=Integer.parseInt(mfks);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
+     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain) 
+        throws IOException, ServletException
+    {
+        HttpServletRequest srequest=(HttpServletRequest)request;
+        if(srequest.getContentType()==null||!srequest.getContentType().startsWith("multipart/form-data"))
+        {
+            chain.doFilter(request,response);
+            return;
+        }
+
+        InputStream in = new BufferedInputStream(request.getInputStream());
+        String content_type=srequest.getContentType();
+        
+        //Get current parameters so we can merge into them
+        MultiMap<String> params = new MultiMap<String>();
+        for (Iterator<Map.Entry<String,String[]>> i = request.getParameterMap().entrySet().iterator();i.hasNext();)
+        {
+            Map.Entry<String,String[]> entry=i.next();
+            Object value=entry.getValue();
+            if (value instanceof String[])
+                params.addValues(entry.getKey(),(String[])value);
+            else
+                params.add(entry.getKey(),value);
+        }
+        
+        MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
+        MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir);
+        mpis.setDeleteOnExit(_deleteFiles);
+        request.setAttribute(MULTIPART, mpis);
+
+        try
+        {
+            Collection<Part> parts = mpis.getParts();
+            if (parts != null)
+            {
+                Iterator<Part> itor = parts.iterator();
+                while (itor.hasNext() && params.size() < _maxFormKeys)
+                {
+                    Part p = itor.next();
+                    MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p;
+                    if (mp.getFile() != null)
+                    {
+                        request.setAttribute(mp.getName(),mp.getFile());
+                        if (mp.getContentDispositionFilename() != null)
+                        {
+                            params.add(mp.getName(), mp.getContentDispositionFilename());
+                            if (mp.getContentType() != null)
+                                params.add(mp.getName()+CONTENT_TYPE_SUFFIX, mp.getContentType());
+                        }
+                    }
+                    else
+                    {
+                        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+                        IO.copy(p.getInputStream(), bytes);
+                        params.add(p.getName(), bytes.toByteArray());
+                        if (p.getContentType() != null)
+                            params.add(p.getName()+CONTENT_TYPE_SUFFIX, p.getContentType());
+                    }
+                }
+            }
+
+            // handle request
+            chain.doFilter(new Wrapper(srequest,params),response);
+        }
+        finally
+        {
+            deleteFiles(request);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private void deleteFiles(ServletRequest request)
+    {
+        if (!_deleteFiles)
+            return;
+        
+        MultiPartInputStream mpis = (MultiPartInputStream)request.getAttribute(MULTIPART);
+        if (mpis != null)
+        {
+            try
+            {
+                mpis.deleteParts();
+            }
+            catch (Exception e)
+            {
+                _context.log("Error deleting multipart tmp files", e);
+            }
+        }
+        request.removeAttribute(MULTIPART);
+    }
+    
+ 
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see javax.servlet.Filter#destroy()
+     */
+    public void destroy()
+    {
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /* ------------------------------------------------------------------------------- */
+    private static class Wrapper extends HttpServletRequestWrapper
+    {
+        String _encoding=StringUtil.__UTF8;
+        MultiMap _params;
+        
+        /* ------------------------------------------------------------------------------- */
+        /** Constructor.
+         * @param request
+         */
+        public Wrapper(HttpServletRequest request, MultiMap map)
+        {
+            super(request);
+            this._params=map;
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#getContentLength()
+         */
+        @Override
+        public int getContentLength()
+        {
+            return 0;
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
+         */
+        @Override
+        public String getParameter(String name)
+        {
+            Object o=_params.get(name);
+            if (!(o instanceof byte[]) && LazyList.size(o)>0)
+                o=LazyList.get(o,0);
+            
+            if (o instanceof byte[])
+            {
+                try
+                {
+                   return getParameterBytesAsString(name, (byte[])o);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn(e);
+                }
+            }
+            else if (o!=null)
+                return String.valueOf(o);
+            return null;
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#getParameterMap()
+         */
+        @Override
+        public Map getParameterMap()
+        {
+            Map<String, String[]> cmap = new HashMap<String,String[]>();
+            
+            for ( Object key : _params.keySet() )
+            {
+                cmap.put((String)key,getParameterValues((String)key));
+            }
+            
+            return Collections.unmodifiableMap(cmap);
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#getParameterNames()
+         */
+        @Override
+        public Enumeration getParameterNames()
+        {
+            return Collections.enumeration(_params.keySet());
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
+         */
+        @Override
+        public String[] getParameterValues(String name)
+        {
+            List l=_params.getValues(name);
+            if (l==null || l.size()==0)
+                return new String[0];
+            String[] v = new String[l.size()];
+            for (int i=0;i<l.size();i++)
+            {
+                Object o=l.get(i);
+                if (o instanceof byte[])
+                {
+                    try
+                    {
+                        v[i]=getParameterBytesAsString(name, (byte[])o);
+                    }
+                    catch(Exception e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else if (o instanceof String)
+                    v[i]=(String)o;
+            }
+            return v;
+        }
+        
+        /* ------------------------------------------------------------------------------- */
+        /**
+         * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
+         */
+        @Override
+        public void setCharacterEncoding(String enc) 
+            throws UnsupportedEncodingException
+        {
+            _encoding=enc;
+        }
+        
+        
+        /* ------------------------------------------------------------------------------- */
+        private String getParameterBytesAsString (String name, byte[] bytes) 
+        throws UnsupportedEncodingException
+        {
+            //check if there is a specific encoding for the parameter
+            Object ct = _params.get(name+CONTENT_TYPE_SUFFIX);
+            //use default if not
+            String contentType = _encoding;
+            if (ct != null)
+            {
+                String tmp = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer((String)ct));
+                contentType = (tmp == null?_encoding:tmp);
+            }
+            
+            return new String(bytes,contentType);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/ProxyServlet.java b/src/java/org/eclipse/jetty/servlets/ProxyServlet.java
new file mode 100644
index 0000000..86f208b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/ProxyServlet.java
@@ -0,0 +1,908 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpExchange;
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.http.HttpHeaderValues;
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.http.PathMap;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.HostMap;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/**
+ * Asynchronous Proxy Servlet.
+ *
+ * Forward requests to another server either as a standard web proxy (as defined by RFC2616) or as a transparent proxy.
+ * <p>
+ * This servlet needs the jetty-util and jetty-client classes to be available to the web application.
+ * <p>
+ * To facilitate JMX monitoring, the "HttpClient" and "ThreadPool" are set as context attributes prefixed with the servlet name.
+ * <p>
+ * The following init parameters may be used to configure the servlet:
+ * <ul>
+ * <li>name - Name of Proxy servlet (default: "ProxyServlet"
+ * <li>maxThreads - maximum threads
+ * <li>maxConnections - maximum connections per destination
+ * <li>timeout - the period in ms the client will wait for a response from the proxied server
+ * <li>idleTimeout - the period in ms a connection to proxied server can be idle for before it is closed
+ * <li>requestHeaderSize - the size of the request header buffer (d. 6,144)
+ * <li>requestBufferSize - the size of the request buffer (d. 12,288)
+ * <li>responseHeaderSize - the size of the response header buffer (d. 6,144)
+ * <li>responseBufferSize - the size of the response buffer (d. 32,768)
+ * <li>HostHeader - Force the host header to a particular value
+ * <li>whiteList - comma-separated list of allowed proxy destinations
+ * <li>blackList - comma-separated list of forbidden proxy destinations
+ * </ul>
+ *
+ * @see org.eclipse.jetty.server.handler.ConnectHandler
+ */
+public class ProxyServlet implements Servlet
+{
+    protected Logger _log;
+    protected HttpClient _client;
+    protected String _hostHeader;
+
+    protected HashSet<String> _DontProxyHeaders = new HashSet<String>();
+    {
+        _DontProxyHeaders.add("proxy-connection");
+        _DontProxyHeaders.add("connection");
+        _DontProxyHeaders.add("keep-alive");
+        _DontProxyHeaders.add("transfer-encoding");
+        _DontProxyHeaders.add("te");
+        _DontProxyHeaders.add("trailer");
+        _DontProxyHeaders.add("proxy-authorization");
+        _DontProxyHeaders.add("proxy-authenticate");
+        _DontProxyHeaders.add("upgrade");
+    }
+
+    protected ServletConfig _config;
+    protected ServletContext _context;
+    protected HostMap<PathMap> _white = new HostMap<PathMap>();
+    protected HostMap<PathMap> _black = new HostMap<PathMap>();
+
+    /* ------------------------------------------------------------ */
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+     */
+    public void init(ServletConfig config) throws ServletException
+    {
+        _config = config;
+        _context = config.getServletContext();
+
+        _hostHeader = config.getInitParameter("HostHeader");
+
+        try
+        {
+            _log = createLogger(config);
+
+            _client = createHttpClient(config);
+
+            if (_context != null)
+            {
+                _context.setAttribute(config.getServletName() + ".ThreadPool",_client.getThreadPool());
+                _context.setAttribute(config.getServletName() + ".HttpClient",_client);
+            }
+
+            String white = config.getInitParameter("whiteList");
+            if (white != null)
+            {
+                parseList(white,_white);
+            }
+            String black = config.getInitParameter("blackList");
+            if (black != null)
+            {
+                parseList(black,_black);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+    }
+
+    public void destroy()
+    {
+        try
+        {
+            _client.stop();
+        }
+        catch (Exception x)
+        {
+            _log.debug(x);
+        }
+    }
+
+
+    /**
+     * Create and return a logger based on the ServletConfig for use in the
+     * proxy servlet
+     *
+     * @param config
+     * @return Logger
+     */
+    protected Logger createLogger(ServletConfig config)
+    {
+        return Log.getLogger("org.eclipse.jetty.servlets." + config.getServletName());
+    }
+
+    /**
+     * Create and return an HttpClientInstance
+     *
+     * @return HttpClient
+     */
+    protected HttpClient createHttpClientInstance()
+    {
+        return new HttpClient();
+    }
+
+    /**
+     * Create and return an HttpClient based on ServletConfig
+     *
+     * By default this implementation will create an instance of the
+     * HttpClient for use by this proxy servlet.
+     *
+     * @param config
+     * @return HttpClient
+     * @throws Exception
+     */
+    protected HttpClient createHttpClient(ServletConfig config) throws Exception
+    {
+        HttpClient client = createHttpClientInstance();
+        client.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
+
+        String t = config.getInitParameter("maxThreads");
+
+        if (t != null)
+        {
+            client.setThreadPool(new QueuedThreadPool(Integer.parseInt(t)));
+        }
+        else
+        {
+            client.setThreadPool(new QueuedThreadPool());
+        }
+
+        ((QueuedThreadPool)client.getThreadPool()).setName(config.getServletName());
+
+        t = config.getInitParameter("maxConnections");
+
+        if (t != null)
+        {
+            client.setMaxConnectionsPerAddress(Integer.parseInt(t));
+        }
+
+        t = config.getInitParameter("timeout");
+
+        if ( t != null )
+        {
+            client.setTimeout(Long.parseLong(t));
+        }
+
+        t = config.getInitParameter("idleTimeout");
+
+        if ( t != null )
+        {
+            client.setIdleTimeout(Long.parseLong(t));
+        }
+
+        t = config.getInitParameter("requestHeaderSize");
+
+        if ( t != null )
+        {
+            client.setRequestHeaderSize(Integer.parseInt(t));
+        }
+
+        t = config.getInitParameter("requestBufferSize");
+
+        if ( t != null )
+        {
+            client.setRequestBufferSize(Integer.parseInt(t));
+        }
+
+        t = config.getInitParameter("responseHeaderSize");
+
+        if ( t != null )
+        {
+            client.setResponseHeaderSize(Integer.parseInt(t));
+        }
+
+        t = config.getInitParameter("responseBufferSize");
+
+        if ( t != null )
+        {
+            client.setResponseBufferSize(Integer.parseInt(t));
+        }
+
+        client.start();
+
+        return client;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Helper function to process a parameter value containing a list of new entries and initialize the specified host map.
+     *
+     * @param list
+     *            comma-separated list of new entries
+     * @param hostMap
+     *            target host map
+     */
+    private void parseList(String list, HostMap<PathMap> hostMap)
+    {
+        if (list != null && list.length() > 0)
+        {
+            int idx;
+            String entry;
+
+            StringTokenizer entries = new StringTokenizer(list,",");
+            while (entries.hasMoreTokens())
+            {
+                entry = entries.nextToken();
+                idx = entry.indexOf('/');
+
+                String host = idx > 0?entry.substring(0,idx):entry;
+                String path = idx > 0?entry.substring(idx):"/*";
+
+                host = host.trim();
+                PathMap pathMap = hostMap.get(host);
+                if (pathMap == null)
+                {
+                    pathMap = new PathMap(true);
+                    hostMap.put(host,pathMap);
+                }
+                if (path != null)
+                {
+                    pathMap.put(path,path);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check the request hostname and path against white- and blacklist.
+     *
+     * @param host
+     *            hostname to check
+     * @param path
+     *            path to check
+     * @return true if request is allowed to be proxied
+     */
+    public boolean validateDestination(String host, String path)
+    {
+        if (_white.size() > 0)
+        {
+            boolean match = false;
+
+            Object whiteObj = _white.getLazyMatches(host);
+            if (whiteObj != null)
+            {
+                List whiteList = (whiteObj instanceof List)?(List)whiteObj:Collections.singletonList(whiteObj);
+
+                for (Object entry : whiteList)
+                {
+                    PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
+                    if (match = (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null)))
+                        break;
+                }
+            }
+
+            if (!match)
+                return false;
+        }
+
+        if (_black.size() > 0)
+        {
+            Object blackObj = _black.getLazyMatches(host);
+            if (blackObj != null)
+            {
+                List blackList = (blackObj instanceof List)?(List)blackObj:Collections.singletonList(blackObj);
+
+                for (Object entry : blackList)
+                {
+                    PathMap pathMap = ((Map.Entry<String, PathMap>)entry).getValue();
+                    if (pathMap != null && (pathMap.size() == 0 || pathMap.match(path) != null))
+                        return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.servlet.Servlet#getServletConfig()
+     */
+    public ServletConfig getServletConfig()
+    {
+        return _config;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the hostHeader.
+     *
+     * @return the hostHeader
+     */
+    public String getHostHeader()
+    {
+        return _hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the hostHeader.
+     *
+     * @param hostHeader
+     *            the hostHeader to set
+     */
+    public void setHostHeader(String hostHeader)
+    {
+        _hostHeader = hostHeader;
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
+     */
+    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException
+    {
+        final int debug = _log.isDebugEnabled()?req.hashCode():0;
+
+        final HttpServletRequest request = (HttpServletRequest)req;
+        final HttpServletResponse response = (HttpServletResponse)res;
+
+        if ("CONNECT".equalsIgnoreCase(request.getMethod()))
+        {
+            handleConnect(request,response);
+        }
+        else
+        {
+            final InputStream in = request.getInputStream();
+            final OutputStream out = response.getOutputStream();
+
+            final Continuation continuation = ContinuationSupport.getContinuation(request);
+
+            if (!continuation.isInitial())
+                response.sendError(HttpServletResponse.SC_GATEWAY_TIMEOUT); // Need better test that isInitial
+            else
+            {
+
+                String uri = request.getRequestURI();
+                if (request.getQueryString() != null)
+                    uri += "?" + request.getQueryString();
+
+                HttpURI url = proxyHttpURI(request,uri);
+
+                if (debug != 0)
+                    _log.debug(debug + " proxy " + uri + "-->" + url);
+
+                if (url == null)
+                {
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                    return;
+                }
+
+                HttpExchange exchange = new HttpExchange()
+                {
+                    @Override
+                    protected void onRequestCommitted() throws IOException
+                    {
+                    }
+
+                    @Override
+                    protected void onRequestComplete() throws IOException
+                    {
+                    }
+
+                    @Override
+                    protected void onResponseComplete() throws IOException
+                    {
+                        if (debug != 0)
+                            _log.debug(debug + " complete");
+                        continuation.complete();
+                    }
+
+                    @Override
+                    protected void onResponseContent(Buffer content) throws IOException
+                    {
+                        if (debug != 0)
+                            _log.debug(debug + " content" + content.length());
+                        content.writeTo(out);
+                    }
+
+                    @Override
+                    protected void onResponseHeaderComplete() throws IOException
+                    {
+                    }
+
+                    @Override
+                    protected void onResponseStatus(Buffer version, int status, Buffer reason) throws IOException
+                    {
+                        if (debug != 0)
+                            _log.debug(debug + " " + version + " " + status + " " + reason);
+
+                        if (reason != null && reason.length() > 0)
+                            response.setStatus(status,reason.toString());
+                        else
+                            response.setStatus(status);
+                    }
+
+                    @Override
+                    protected void onResponseHeader(Buffer name, Buffer value) throws IOException
+                    {
+                        String nameString = name.toString();
+                        String s = nameString.toLowerCase(Locale.ENGLISH);
+                        if (!_DontProxyHeaders.contains(s) || (HttpHeaders.CONNECTION_BUFFER.equals(name) && HttpHeaderValues.CLOSE_BUFFER.equals(value)))
+                        {
+                            if (debug != 0)
+                                _log.debug(debug + " " + name + ": " + value);
+
+                            String filteredHeaderValue = filterResponseHeaderValue(nameString,value.toString(),request);
+                            if (filteredHeaderValue != null && filteredHeaderValue.trim().length() > 0)
+                            {
+                                if (debug != 0)
+                                    _log.debug(debug + " " + name + ": (filtered): " + filteredHeaderValue);
+                                response.addHeader(nameString,filteredHeaderValue);
+                            }
+                        }
+                        else if (debug != 0)
+                            _log.debug(debug + " " + name + "! " + value);
+                    }
+
+                    @Override
+                    protected void onConnectionFailed(Throwable ex)
+                    {
+                        handleOnConnectionFailed(ex,request,response);
+
+                        // it is possible this might trigger before the
+                        // continuation.suspend()
+                        if (!continuation.isInitial())
+                        {
+                            continuation.complete();
+                        }
+                    }
+
+                    @Override
+                    protected void onException(Throwable ex)
+                    {
+                        if (ex instanceof EofException)
+                        {
+                            _log.ignore(ex);
+                            //return;
+                        }
+                        handleOnException(ex,request,response);
+
+                        // it is possible this might trigger before the
+                        // continuation.suspend()
+                        if (!continuation.isInitial())
+                        {
+                            continuation.complete();
+                        }
+                    }
+
+                    @Override
+                    protected void onExpire()
+                    {
+                        handleOnExpire(request,response);
+                        continuation.complete();
+                    }
+
+                };
+
+                exchange.setScheme(HttpSchemes.HTTPS.equals(request.getScheme())?HttpSchemes.HTTPS_BUFFER:HttpSchemes.HTTP_BUFFER);
+                exchange.setMethod(request.getMethod());
+                exchange.setURL(url.toString());
+                exchange.setVersion(request.getProtocol());
+
+
+                if (debug != 0)
+                    _log.debug(debug + " " + request.getMethod() + " " + url + " " + request.getProtocol());
+
+                // check connection header
+                String connectionHdr = request.getHeader("Connection");
+                if (connectionHdr != null)
+                {
+                    connectionHdr = connectionHdr.toLowerCase(Locale.ENGLISH);
+                    if (connectionHdr.indexOf("keep-alive") < 0 && connectionHdr.indexOf("close") < 0)
+                        connectionHdr = null;
+                }
+
+                // force host
+                if (_hostHeader != null)
+                    exchange.setRequestHeader("Host",_hostHeader);
+
+                // copy headers
+                boolean xForwardedFor = false;
+                boolean hasContent = false;
+                long contentLength = -1;
+                Enumeration<?> enm = request.getHeaderNames();
+                while (enm.hasMoreElements())
+                {
+                    // TODO could be better than this!
+                    String hdr = (String)enm.nextElement();
+                    String lhdr = hdr.toLowerCase(Locale.ENGLISH);
+
+                    if ("transfer-encoding".equals(lhdr))
+                    {
+                        if (request.getHeader("transfer-encoding").indexOf("chunk")>=0)
+                            hasContent = true;
+                    }
+                    
+                    if (_DontProxyHeaders.contains(lhdr))
+                        continue;
+                    if (connectionHdr != null && connectionHdr.indexOf(lhdr) >= 0)
+                        continue;
+                    if (_hostHeader != null && "host".equals(lhdr))
+                        continue;
+
+                    if ("content-type".equals(lhdr))
+                        hasContent = true;
+                    else if ("content-length".equals(lhdr))
+                    {
+                        contentLength = request.getContentLength();
+                        exchange.setRequestHeader(HttpHeaders.CONTENT_LENGTH,Long.toString(contentLength));
+                        if (contentLength > 0)
+                            hasContent = true;
+                    }
+                    else if ("x-forwarded-for".equals(lhdr))
+                        xForwardedFor = true;
+
+                    Enumeration<?> vals = request.getHeaders(hdr);
+                    while (vals.hasMoreElements())
+                    {
+                        String val = (String)vals.nextElement();
+                        if (val != null)
+                        {
+                            if (debug != 0)
+                                _log.debug(debug + " " + hdr + ": " + val);
+
+                            exchange.setRequestHeader(hdr,val);
+                        }
+                    }
+                }
+
+                // Proxy headers
+                exchange.setRequestHeader("Via","1.1 (jetty)");
+                if (!xForwardedFor)
+                {
+                    exchange.addRequestHeader("X-Forwarded-For",request.getRemoteAddr());
+                    exchange.addRequestHeader("X-Forwarded-Proto",request.getScheme());
+                    exchange.addRequestHeader("X-Forwarded-Host",request.getHeader("Host"));
+                    exchange.addRequestHeader("X-Forwarded-Server",request.getLocalName());
+                }
+
+                if (hasContent)
+                {
+                    exchange.setRequestContentSource(in);
+                }
+
+                customizeExchange(exchange, request);
+
+                /*
+                 * we need to set the timeout on the continuation to take into
+                 * account the timeout of the HttpClient and the HttpExchange
+                 */
+                long ctimeout = (_client.getTimeout() > exchange.getTimeout()) ? _client.getTimeout() : exchange.getTimeout();
+
+                // continuation fudge factor of 1000, underlying components
+                // should fail/expire first from exchange
+                if ( ctimeout == 0 )
+                {
+                    continuation.setTimeout(0);  // ideally never times out
+                }
+                else
+                {
+                    continuation.setTimeout(ctimeout + 1000);
+                }
+
+                customizeContinuation(continuation);
+
+                continuation.suspend(response);
+                _client.send(exchange);
+
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handleConnect(HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        String uri = request.getRequestURI();
+
+        String port = "";
+        String host = "";
+
+        int c = uri.indexOf(':');
+        if (c >= 0)
+        {
+            port = uri.substring(c + 1);
+            host = uri.substring(0,c);
+            if (host.indexOf('/') > 0)
+                host = host.substring(host.indexOf('/') + 1);
+        }
+
+        // TODO - make this async!
+
+        InetSocketAddress inetAddress = new InetSocketAddress(host,Integer.parseInt(port));
+
+        // if (isForbidden(HttpMessage.__SSL_SCHEME,addrPort.getHost(),addrPort.getPort(),false))
+        // {
+        // sendForbid(request,response,uri);
+        // }
+        // else
+        {
+            InputStream in = request.getInputStream();
+            OutputStream out = response.getOutputStream();
+
+            Socket socket = new Socket(inetAddress.getAddress(),inetAddress.getPort());
+
+            response.setStatus(200);
+            response.setHeader("Connection","close");
+            response.flushBuffer();
+            // TODO prevent real close!
+
+            IO.copyThread(socket.getInputStream(),out);
+            IO.copy(in,socket.getOutputStream());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected HttpURI proxyHttpURI(HttpServletRequest request, String uri) throws MalformedURLException
+    {
+        return proxyHttpURI(request.getScheme(), request.getServerName(), request.getServerPort(), uri);
+    }
+
+    protected HttpURI proxyHttpURI(String scheme, String serverName, int serverPort, String uri) throws MalformedURLException
+    {
+        if (!validateDestination(serverName,uri))
+            return null;
+
+        return new HttpURI(scheme + "://" + serverName + ":" + serverPort + uri);
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see javax.servlet.Servlet#getServletInfo()
+     */
+    public String getServletInfo()
+    {
+        return "Proxy Servlet";
+    }
+
+
+    /**
+     * Extension point for subclasses to customize an exchange. Useful for setting timeouts etc. The default implementation does nothing.
+     *
+     * @param exchange
+     * @param request
+     */
+    protected void customizeExchange(HttpExchange exchange, HttpServletRequest request)
+    {
+
+    }
+
+    /**
+     * Extension point for subclasses to customize the Continuation after it's initial creation in the service method. Useful for setting timeouts etc. The
+     * default implementation does nothing.
+     *
+     * @param continuation
+     */
+    protected void customizeContinuation(Continuation continuation)
+    {
+
+    }
+
+    /**
+     * Extension point for custom handling of an HttpExchange's onConnectionFailed method. The default implementation delegates to
+     * {@link #handleOnException(Throwable, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+     *
+     * @param ex
+     * @param request
+     * @param response
+     */
+    protected void handleOnConnectionFailed(Throwable ex, HttpServletRequest request, HttpServletResponse response)
+    {
+        handleOnException(ex,request,response);
+    }
+
+    /**
+     * Extension point for custom handling of an HttpExchange's onException method. The default implementation sets the response status to
+     * HttpServletResponse.SC_INTERNAL_SERVER_ERROR (503)
+     *
+     * @param ex
+     * @param request
+     * @param response
+     */
+    protected void handleOnException(Throwable ex, HttpServletRequest request, HttpServletResponse response)
+    {
+        if (ex instanceof IOException)
+        {
+            _log.warn(ex.toString());
+            _log.debug(ex);
+        }
+        else
+            _log.warn(ex);
+        
+        if (!response.isCommitted())
+        {
+            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    /**
+     * Extension point for custom handling of an HttpExchange's onExpire method. The default implementation sets the response status to
+     * HttpServletResponse.SC_GATEWAY_TIMEOUT (504)
+     *
+     * @param request
+     * @param response
+     */
+    protected void handleOnExpire(HttpServletRequest request, HttpServletResponse response)
+    {
+        if (!response.isCommitted())
+        {
+            response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
+        }
+    }
+
+    /**
+     * Extension point for remote server response header filtering. The default implementation returns the header value as is. If null is returned, this header
+     * won't be forwarded back to the client.
+     * 
+     * @param headerName
+     * @param headerValue
+     * @param request
+     * @return filteredHeaderValue
+     */
+    protected String filterResponseHeaderValue(String headerName, String headerValue, HttpServletRequest request)
+    {
+        return headerValue;
+    }
+
+    /**
+     * Transparent Proxy.
+     * 
+     * This convenience extension to ProxyServlet configures the servlet as a transparent proxy. The servlet is configured with init parameters:
+     * <ul>
+     * <li>ProxyTo - a URI like http://host:80/context to which the request is proxied.
+     * <li>Prefix - a URI prefix that is striped from the start of the forwarded URI.
+     * </ul>
+     * For example, if a request was received at /foo/bar and the ProxyTo was http://host:80/context and the Prefix was /foo, then the request would be proxied
+     * to http://host:80/context/bar
+     * 
+     */
+    public static class Transparent extends ProxyServlet
+    {
+        String _prefix;
+        String _proxyTo;
+
+        public Transparent()
+        {
+        }
+
+        public Transparent(String prefix, String host, int port)
+        {
+            this(prefix,"http",host,port,null);
+        }
+
+        public Transparent(String prefix, String schema, String host, int port, String path)
+        {
+            try
+            {
+                if (prefix != null)
+                {
+                    _prefix = new URI(prefix).normalize().toString();
+                }
+                _proxyTo = new URI(schema,null,host,port,path,null,null).normalize().toString();
+            }
+            catch (URISyntaxException ex)
+            {
+                _log.debug("Invalid URI syntax",ex);
+            }
+        }
+
+        @Override
+        public void init(ServletConfig config) throws ServletException
+        {
+            super.init(config);
+
+            String prefix = config.getInitParameter("Prefix");
+            _prefix = prefix == null?_prefix:prefix;
+
+            // Adjust prefix value to account for context path
+            String contextPath = _context.getContextPath();
+            _prefix = _prefix == null?contextPath:(contextPath + _prefix);
+
+            String proxyTo = config.getInitParameter("ProxyTo");
+            _proxyTo = proxyTo == null?_proxyTo:proxyTo;
+
+            if (_proxyTo == null)
+                throw new UnavailableException("ProxyTo parameter is requred.");
+
+            if (!_prefix.startsWith("/"))
+                throw new UnavailableException("Prefix parameter must start with a '/'.");
+
+            _log.info(config.getServletName() + " @ " + _prefix + " to " + _proxyTo);
+        }
+
+        @Override
+        protected HttpURI proxyHttpURI(final String scheme, final String serverName, int serverPort, final String uri) throws MalformedURLException
+        {
+            try
+            {
+                if (!uri.startsWith(_prefix))
+                    return null;
+
+                URI dstUri = new URI(_proxyTo + uri.substring(_prefix.length())).normalize();
+
+                if (!validateDestination(dstUri.getHost(),dstUri.getPath()))
+                    return null;
+
+                return new HttpURI(dstUri.toString());
+            }
+            catch (URISyntaxException ex)
+            {
+                throw new MalformedURLException(ex.getMessage());
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/PutFilter.java b/src/java/org/eclipse/jetty/servlets/PutFilter.java
new file mode 100644
index 0000000..fa64e24
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/PutFilter.java
@@ -0,0 +1,374 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.servlet.Filter;
+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.UnavailableException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * PutFilter
+ * 
+ * A Filter that handles PUT, DELETE and MOVE methods.
+ * Files are hidden during PUT operations, so that 404's result.
+ * 
+ * The following init parameters pay be used:<ul>
+ * <li><b>baseURI</b> - The file URI of the document root for put content.
+ * <li><b>delAllowed</b> - boolean, if true DELETE and MOVE methods are supported.
+ * <li><b>putAtomic</b> - boolean, if true PUT files are written to a temp location and moved into place.
+ * </ul>
+ *
+ */
+public class PutFilter implements Filter 
+{
+    public final static String __PUT="PUT";
+    public final static String __DELETE="DELETE";
+    public final static String __MOVE="MOVE";
+    public final static String __OPTIONS="OPTIONS";
+
+    Set<String> _operations = new HashSet<String>();
+    private ConcurrentMap<String,String> _hidden = new ConcurrentHashMap<String, String>();
+
+    private ServletContext _context;
+    private String _baseURI;
+    private boolean _delAllowed;
+    private boolean _putAtomic;
+    private File _tmpdir;
+    
+    
+    /* ------------------------------------------------------------ */
+    public void init(FilterConfig config) throws ServletException
+    {
+        _context=config.getServletContext();
+        
+        _tmpdir=(File)_context.getAttribute("javax.servlet.context.tempdir");
+            
+        if (_context.getRealPath("/")==null)
+           throw new UnavailableException("Packed war");
+        
+        String b = config.getInitParameter("baseURI");
+        if (b != null)
+        {
+            _baseURI=b;
+        }
+        else
+        {
+            File base=new File(_context.getRealPath("/"));
+            _baseURI=base.toURI().toString();
+        }
+        
+        _delAllowed = getInitBoolean(config,"delAllowed");
+        _putAtomic = getInitBoolean(config,"putAtomic");
+
+        _operations.add(__OPTIONS);
+        _operations.add(__PUT);
+        if (_delAllowed)
+        {
+            _operations.add(__DELETE);
+            _operations.add(__MOVE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean getInitBoolean(FilterConfig config,String name)
+    {
+        String value = config.getInitParameter(name);
+        return value != null && value.length() > 0 && (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1"));
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
+    {
+        HttpServletRequest request=(HttpServletRequest)req;
+        HttpServletResponse response=(HttpServletResponse)res;
+
+        String servletPath =request.getServletPath();
+        String pathInfo = request.getPathInfo();
+        String pathInContext = URIUtil.addPaths(servletPath, pathInfo);    
+
+        String resource = URIUtil.addPaths(_baseURI,pathInContext); 
+       
+        String method = request.getMethod();
+        boolean op = _operations.contains(method);
+        
+        if (op)
+        {
+            File file = null;
+            try
+            {
+                if (method.equals(__OPTIONS))
+                    handleOptions(chain,request, response);
+                else
+                {
+                    file=new File(new URI(resource));
+                    boolean exists = file.exists();
+                    if (exists && !passConditionalHeaders(request, response, file))
+                        return;
+                    
+                    if (method.equals(__PUT))
+                        handlePut(request, response,pathInContext, file);
+                    else if (method.equals(__DELETE))
+                        handleDelete(request, response, pathInContext, file);
+                    else if (method.equals(__MOVE))
+                        handleMove(request, response, pathInContext, file);
+                    else
+                        throw new IllegalStateException();
+                }
+            }
+            catch(Exception e)
+            {
+                _context.log(e.toString(),e);
+                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            }
+        }
+        else
+        {
+            if (isHidden(pathInContext))
+                response.sendError(HttpServletResponse.SC_NOT_FOUND);
+            else
+                chain.doFilter(request,response);
+            return;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean isHidden(String pathInContext)
+    {
+        return _hidden.containsKey(pathInContext);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void destroy()
+    {
+    }
+
+    /* ------------------------------------------------------------------- */
+    public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
+    {
+        boolean exists = file.exists();
+        if (pathInContext.endsWith("/"))
+        {
+            if (!exists)
+            {
+                if (!file.mkdirs())
+                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                else
+                {
+                    response.setStatus(HttpServletResponse.SC_CREATED);
+                    response.flushBuffer();
+                }
+            }
+            else
+            {
+                response.setStatus(HttpServletResponse.SC_OK);
+                response.flushBuffer();
+            }
+        }
+        else
+        {
+            boolean ok=false;
+            try
+            {
+                _hidden.put(pathInContext,pathInContext);
+                File parent = file.getParentFile();
+                parent.mkdirs();
+                int toRead = request.getContentLength();
+                InputStream in = request.getInputStream();
+                
+                    
+                if (_putAtomic)
+                {
+                    File tmp=File.createTempFile(file.getName(),null,_tmpdir);
+                    OutputStream out = new FileOutputStream(tmp,false);
+                    if (toRead >= 0)
+                        IO.copy(in, out, toRead);
+                    else
+                        IO.copy(in, out);
+                    out.close();
+                    
+                    if (!tmp.renameTo(file))
+                        throw new IOException("rename from "+tmp+" to "+file+" failed");
+                }
+                else
+                {
+                    OutputStream out = new FileOutputStream(file,false);
+                    if (toRead >= 0)
+                        IO.copy(in, out, toRead);
+                    else
+                        IO.copy(in, out);
+                    out.close();
+                }
+
+                response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
+                response.flushBuffer();
+                ok=true;
+            }
+            catch (Exception ex)
+            {
+                _context.log(ex.toString(),ex);
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+            }
+            finally
+            {
+                if (!ok)
+                {
+                    try
+                    {
+                        if (file.exists())
+                            file.delete();
+                    }
+                    catch(Exception e)
+                    {
+                        _context.log(e.toString(),e);
+                    }
+                }
+                _hidden.remove(pathInContext);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------------- */
+    public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
+    {
+        try
+        {
+            // delete the file
+            if (file.delete())
+            {
+                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+                response.flushBuffer();
+            }
+            else
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+        }
+        catch (SecurityException sex)
+        {
+            _context.log(sex.toString(),sex);
+            response.sendError(HttpServletResponse.SC_FORBIDDEN);
+        }
+    }
+
+    /* ------------------------------------------------------------------- */
+    public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) 
+        throws ServletException, IOException, URISyntaxException
+    {
+        String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
+        if (newPath == null)
+        {
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        
+        String contextPath = request.getContextPath();
+        if (contextPath != null && !newPath.startsWith(contextPath))
+        {
+            response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+            return;
+        }
+        String newInfo = newPath;
+        if (contextPath != null)
+            newInfo = newInfo.substring(contextPath.length());
+
+        String new_resource = URIUtil.addPaths(_baseURI,newInfo);
+        File new_file=new File(new URI(new_resource));
+
+        file.renameTo(new_file);
+
+        response.setStatus(HttpServletResponse.SC_NO_CONTENT);
+        response.flushBuffer();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handleOptions(FilterChain chain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        chain.doFilter(request,new HttpServletResponseWrapper(response)
+        {
+            @Override
+            public void setHeader(String name, String value)
+            {
+                if ("Allow".equalsIgnoreCase(name))
+                {
+                    Set<String> options = new HashSet<String>();
+                    options.addAll(Arrays.asList(value.split(" *, *")));
+                    options.addAll(_operations);
+                    value=null;
+                    for (String o : options)
+                        value=value==null?o:(value+", "+o);
+                }
+                    
+                super.setHeader(name,value);
+            }
+        });
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Check modification date headers.
+     */
+    protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
+    {
+        long date = 0;
+        
+        if ((date = request.getDateHeader("if-unmodified-since")) > 0)
+        {
+            if (file.lastModified() / 1000 > date / 1000)
+            {
+                response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
+                return false;
+            }
+        }
+
+        if ((date = request.getDateHeader("if-modified-since")) > 0)
+        {
+            if (file.lastModified() / 1000 <= date / 1000)
+            {
+                response.reset();
+                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+                response.flushBuffer();
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/QoSFilter.java b/src/java/org/eclipse/jetty/servlets/QoSFilter.java
new file mode 100644
index 0000000..8844789
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/QoSFilter.java
@@ -0,0 +1,347 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.Filter;
+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.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.eclipse.jetty.continuation.Continuation;
+import org.eclipse.jetty.continuation.ContinuationListener;
+import org.eclipse.jetty.continuation.ContinuationSupport;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * Quality of Service Filter.
+ * 
+ * This filter limits the number of active requests to the number set by the "maxRequests" init parameter (default 10).
+ * If more requests are received, they are suspended and placed on priority queues.  Priorities are determined by 
+ * the {@link #getPriority(ServletRequest)} method and are a value between 0 and the value given by the "maxPriority" 
+ * init parameter (default 10), with higher values having higher priority.
+ * </p><p>
+ * This filter is ideal to prevent wasting threads waiting for slow/limited 
+ * resources such as a JDBC connection pool.  It avoids the situation where all of a 
+ * containers thread pool may be consumed blocking on such a slow resource.
+ * By limiting the number of active threads, a smaller thread pool may be used as 
+ * the threads are not wasted waiting.  Thus more memory may be available for use by 
+ * the active threads.
+ * </p><p>
+ * Furthermore, this filter uses a priority when resuming waiting requests. So that if
+ * a container is under load, and there are many requests waiting for resources,
+ * the {@link #getPriority(ServletRequest)} method is used, so that more important 
+ * requests are serviced first.     For example, this filter could be deployed with a 
+ * maxRequest limit slightly smaller than the containers thread pool and a high priority 
+ * allocated to admin users.  Thus regardless of load, admin users would always be
+ * able to access the web application.
+ * </p><p>
+ * The maxRequest limit is policed by a {@link Semaphore} and the filter will wait a short while attempting to acquire
+ * the semaphore. This wait is controlled by the "waitMs" init parameter and allows the expense of a suspend to be
+ * avoided if the semaphore is shortly available.  If the semaphore cannot be obtained, the request will be suspended
+ * for the default suspend period of the container or the valued set as the "suspendMs" init parameter.
+ * </p><p>
+ * If the "managedAttr" init parameter is set to true, then this servlet is set as a {@link ServletContext} attribute with the 
+ * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
+ * manage the configuration of the filter.
+ * </p>
+ * 
+ *
+ */
+public class QoSFilter implements Filter
+{
+    final static int __DEFAULT_MAX_PRIORITY=10;
+    final static int __DEFAULT_PASSES=10;
+    final static int __DEFAULT_WAIT_MS=50;
+    final static long __DEFAULT_TIMEOUT_MS = -1;
+    
+    final static String MANAGED_ATTR_INIT_PARAM="managedAttr";
+    final static String MAX_REQUESTS_INIT_PARAM="maxRequests";
+    final static String MAX_PRIORITY_INIT_PARAM="maxPriority";
+    final static String MAX_WAIT_INIT_PARAM="waitMs";
+    final static String SUSPEND_INIT_PARAM="suspendMs";
+    
+    ServletContext _context;
+
+    protected long _waitMs;
+    protected long _suspendMs;
+    protected int _maxRequests;
+    
+    private Semaphore _passes;
+    private Queue<Continuation>[] _queue;
+    private ContinuationListener[] _listener;
+    private String _suspended="QoSFilter@"+this.hashCode();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+     */
+    public void init(FilterConfig filterConfig) 
+    {
+        _context=filterConfig.getServletContext();
+
+        int max_priority=__DEFAULT_MAX_PRIORITY;
+        if (filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM)!=null)
+            max_priority=Integer.parseInt(filterConfig.getInitParameter(MAX_PRIORITY_INIT_PARAM));
+        _queue=new Queue[max_priority+1];
+        _listener = new ContinuationListener[max_priority + 1];
+        for (int p=0;p<_queue.length;p++)
+        {
+            _queue[p]=new ConcurrentLinkedQueue<Continuation>();
+
+            final int priority=p;
+            _listener[p] = new ContinuationListener()
+            {
+                public void onComplete(Continuation continuation)
+                {}
+
+                public void onTimeout(Continuation continuation)
+                {
+                    _queue[priority].remove(continuation);
+                }
+            };
+        }
+        
+        int maxRequests=__DEFAULT_PASSES;
+        if (filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM)!=null)
+            maxRequests=Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_INIT_PARAM));
+        _passes=new Semaphore(maxRequests,true);
+        _maxRequests = maxRequests;
+        
+        long wait = __DEFAULT_WAIT_MS;
+        if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM)!=null)
+            wait=Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
+        _waitMs=wait;
+        
+        long suspend = __DEFAULT_TIMEOUT_MS;
+        if (filterConfig.getInitParameter(SUSPEND_INIT_PARAM)!=null)
+            suspend=Integer.parseInt(filterConfig.getInitParameter(SUSPEND_INIT_PARAM));
+        _suspendMs=suspend;
+
+        if (_context!=null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
+            _context.setAttribute(filterConfig.getFilterName(),this);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
+    throws IOException, ServletException
+    {
+        boolean accepted=false;
+        try
+        {
+            if (request.getAttribute(_suspended)==null)
+            {
+                accepted=_passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
+                if (accepted)
+                {
+                    request.setAttribute(_suspended,Boolean.FALSE);
+                }
+                else
+                {
+                    request.setAttribute(_suspended,Boolean.TRUE);
+                    int priority = getPriority(request);
+                    Continuation continuation = ContinuationSupport.getContinuation(request);
+                    if (_suspendMs>0)
+                        continuation.setTimeout(_suspendMs);
+                    continuation.suspend();
+                    continuation.addContinuationListener(_listener[priority]);
+                    _queue[priority].add(continuation);
+                    return;
+                }
+            }
+            else
+            {
+                Boolean suspended=(Boolean)request.getAttribute(_suspended);
+                
+                if (suspended.booleanValue())
+                {
+                    request.setAttribute(_suspended,Boolean.FALSE);
+                    if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE)
+                    {
+                        _passes.acquire();
+                        accepted=true;
+                    }
+                    else 
+                    {
+                        // Timeout! try 1 more time.
+                        accepted = _passes.tryAcquire(_waitMs,TimeUnit.MILLISECONDS);
+                    }
+                }
+                else
+                {
+                    // pass through resume of previously accepted request
+                    _passes.acquire();
+                    accepted = true;
+                }
+            }
+
+            if (accepted)
+            {
+                chain.doFilter(request,response);
+            }
+            else
+            {
+                ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            }
+        }
+        catch(InterruptedException e)
+        {
+            _context.log("QoS",e);
+            ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+        }
+        finally
+        {
+            if (accepted)
+            {
+                for (int p=_queue.length;p-->0;)
+                {
+                    Continuation continutaion=_queue[p].poll();
+                    if (continutaion!=null && continutaion.isSuspended())
+                    {
+                        continutaion.resume();
+                        break;
+                    }
+                }
+                _passes.release();
+            }
+        }
+    }
+
+    /** 
+     * Get the request Priority.
+     * <p> The default implementation assigns the following priorities:<ul>
+     * <li> 2 - for a authenticated request
+     * <li> 1 - for a request with valid /non new session 
+     * <li> 0 - for all other requests.
+     * </ul>
+     * This method may be specialised to provide application specific priorities.
+     * 
+     * @param request
+     * @return the request priority
+     */
+    protected int getPriority(ServletRequest request)
+    {
+        HttpServletRequest baseRequest = (HttpServletRequest)request;
+        if (baseRequest.getUserPrincipal() != null )
+            return 2;
+        else 
+        {
+            HttpSession session = baseRequest.getSession(false);
+            if (session!=null && !session.isNew()) 
+                return 1;
+            else
+                return 0;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.Filter#destroy()
+     */
+    public void destroy(){}
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * Get the (short) amount of time (in milliseconds) that the filter would wait
+     * for the semaphore to become available before suspending a request.
+     * 
+     * @return wait time (in milliseconds)
+     */
+    public long getWaitMs()
+    {
+        return _waitMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the (short) amount of time (in milliseconds) that the filter would wait
+     * for the semaphore to become available before suspending a request.
+     * 
+     * @param value wait time (in milliseconds)
+     */
+    public void setWaitMs(long value)
+    {
+        _waitMs = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the amount of time (in milliseconds) that the filter would suspend
+     * a request for while waiting for the semaphore to become available.
+     * 
+     * @return suspend time (in milliseconds)
+     */
+    public long getSuspendMs()
+    {
+        return _suspendMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the amount of time (in milliseconds) that the filter would suspend
+     * a request for while waiting for the semaphore to become available.
+     * 
+     * @param value suspend time (in milliseconds)
+     */
+    public void setSuspendMs(long value)
+    {
+        _suspendMs = value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the maximum number of requests allowed to be processed
+     * at the same time.
+     * 
+     * @return maximum number of requests
+     */
+    public int getMaxRequests()
+    {
+        return _maxRequests;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the maximum number of requests allowed to be processed
+     * at the same time.
+     * 
+     * @param value the number of requests
+     */
+    public void setMaxRequests(int value)
+    {
+        _passes = new Semaphore((value-_maxRequests+_passes.availablePermits()), true);
+        _maxRequests = value;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/servlets/UserAgentFilter.java b/src/java/org/eclipse/jetty/servlets/UserAgentFilter.java
new file mode 100644
index 0000000..3ab465e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/UserAgentFilter.java
@@ -0,0 +1,157 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/* ------------------------------------------------------------ */
+/** User Agent Filter.
+ * <p>
+ * This filter allows efficient matching of user agent strings for
+ * downstream or extended filters to use for browser specific logic.
+ * </p>
+ * <p>
+ * The filter is configured with the following init parameters:
+ * <dl>
+ * <dt>attribute</dt><dd>If set, then the request attribute of this name is set with the matched user agent string</dd>
+ * <dt>cacheSize</dt><dd>The size of the user-agent cache, used to avoid reparsing of user agent strings. The entire cache is flushed
+ * when this size is reached</dd>
+ * <dt>userAgent</dt><dd>A regex {@link Pattern} to extract the essential elements of the user agent.
+ * The concatenation of matched pattern groups is used as the user agent name</dd>
+ * <dl>
+ * An example value for pattern is <code>(?:Mozilla[^\(]*\(compatible;\s*+([^;]*);.*)|(?:.*?([^\s]+/[^\s]+).*)</code>. These two
+ * pattern match the common compatibility user-agent strings and extract the real user agent, failing that, the first
+ * element of the agent string is returned.
+ *
+ *
+ */
+public class UserAgentFilter implements Filter
+{
+    private static final String __defaultPattern = "(?:Mozilla[^\\(]*\\(compatible;\\s*+([^;]*);.*)|(?:.*?([^\\s]+/[^\\s]+).*)";
+    private Pattern _pattern = Pattern.compile(__defaultPattern);
+    private Map<String, String> _agentCache = new ConcurrentHashMap<String, String>();
+    private int _agentCacheSize=1024;
+    private String _attribute;
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.Filter#destroy()
+     */
+    public void destroy()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     */
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+    {
+        if (_attribute!=null && _pattern!=null)
+        {
+            String ua=getUserAgent(request);
+            request.setAttribute(_attribute,ua);
+        }
+        chain.doFilter(request,response);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+     */
+    public void init(FilterConfig filterConfig) throws ServletException
+    {
+        _attribute=filterConfig.getInitParameter("attribute");
+
+        String p=filterConfig.getInitParameter("userAgent");
+        if (p!=null)
+            _pattern=Pattern.compile(p);
+
+        String size=filterConfig.getInitParameter("cacheSize");
+        if (size!=null)
+            _agentCacheSize=Integer.parseInt(size);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getUserAgent(ServletRequest request)
+    {
+        String ua=((HttpServletRequest)request).getHeader("User-Agent");
+        return getUserAgent(ua);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get UserAgent.
+     * The configured agent patterns are used to match against the passed user agent string.
+     * If any patterns match, the concatenation of pattern groups is returned as the user agent
+     * string. Match results are cached.
+     * @param ua A user agent string
+     * @return The matched pattern groups or the original user agent string
+     */
+    public String getUserAgent(String ua)
+    {
+        if (ua == null)
+            return null;
+
+        String tag = _agentCache.get(ua);
+
+        if (tag == null)
+        {
+            if (_pattern != null)
+            {
+                Matcher matcher = _pattern.matcher(ua);
+                if (matcher.matches())
+                {
+                    if (matcher.groupCount() > 0)
+                    {
+                        for (int g = 1; g <= matcher.groupCount(); g++)
+                        {
+                            String group = matcher.group(g);
+                            if (group != null)
+                                tag = tag == null ? group : tag + group;
+                        }
+                    }
+                    else
+                    {
+                        tag = matcher.group();
+                    }
+                }
+            }
+
+            if (tag == null)
+                tag = ua;
+
+            if (_agentCache.size() >= _agentCacheSize)
+                _agentCache.clear();
+            _agentCache.put(ua, tag);
+        }
+
+        return tag;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/src/java/org/eclipse/jetty/servlets/WelcomeFilter.java
new file mode 100644
index 0000000..7d9a3b3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/servlets/WelcomeFilter.java
@@ -0,0 +1,70 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.servlets;
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+/* ------------------------------------------------------------ */
+/** Welcome Filter
+ * This filter can be used to server an index file for a directory 
+ * when no index file actually exists (thus the web.xml mechanism does
+ * not work).
+ * 
+ * This filter will dispatch requests to a directory (URLs ending with /)
+ * to the welcome URL determined by the "welcome" init parameter.  So if
+ * the filter "welcome" init parameter is set to "index.do" then a request
+ * to "/some/directory/" will be dispatched to "/some/directory/index.do" and
+ * will be handled by any servlets mapped to that URL.
+ *
+ * Requests to "/some/directory" will be redirected to "/some/directory/".
+ */
+public  class WelcomeFilter implements Filter
+{
+    private String welcome;
+    
+    public void init(FilterConfig filterConfig)
+    {
+        welcome=filterConfig.getInitParameter("welcome");
+	if (welcome==null)
+	    welcome="index.html";
+    }
+
+    /* ------------------------------------------------------------ */
+    public void doFilter(ServletRequest request,
+                         ServletResponse response,
+                         FilterChain chain)
+	throws IOException, ServletException
+    {
+        String path=((HttpServletRequest)request).getServletPath();
+        if (welcome!=null && path.endsWith("/"))
+            request.getRequestDispatcher(path+welcome).forward(request,response);
+        else
+            chain.doFilter(request, response);
+    }
+
+    public void destroy() {}
+}
+
diff --git a/src/java/org/eclipse/jetty/util/ArrayQueue.java b/src/java/org/eclipse/jetty/util/ArrayQueue.java
new file mode 100644
index 0000000..7554169
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ArrayQueue.java
@@ -0,0 +1,379 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+
+/* ------------------------------------------------------------ */
+/**
+ * Queue backed by circular array.
+ * <p/>
+ * This partial Queue implementation (also with {@link #remove()} for stack operation)
+ * is backed by a growable circular array.
+ *
+ * @param <E>
+ */
+public class ArrayQueue<E> extends AbstractList<E> implements Queue<E>
+{
+    public static final int DEFAULT_CAPACITY = 64;
+    public static final int DEFAULT_GROWTH = 32;
+
+    protected final Object _lock;
+    protected final int _growCapacity;
+    protected Object[] _elements;
+    protected int _nextE;
+    protected int _nextSlot;
+    protected int _size;
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue()
+    {
+        this(DEFAULT_CAPACITY, -1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int capacity)
+    {
+        this(capacity, -1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy)
+    {
+        this(initCapacity, growBy, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public ArrayQueue(int initCapacity, int growBy, Object lock)
+    {
+        _lock = lock == null ? this : lock;
+        _growCapacity = growBy;
+        _elements = new Object[initCapacity];
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getCapacity()
+    {
+        synchronized (_lock)
+        {
+            return _elements.length;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean add(E e)
+    {
+        if (!offer(e))
+            throw new IllegalStateException("Full");
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean offer(E e)
+    {
+        synchronized (_lock)
+        {
+            return enqueue(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean enqueue(E e)
+    {
+        if (_size == _elements.length && !grow())
+            return false;
+
+        _size++;
+        _elements[_nextSlot++] = e;
+        if (_nextSlot == _elements.length)
+            _nextSlot = 0;
+
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add without synchronization or bounds checking
+     *
+     * @param e the element to add
+     * @see #add(Object)
+     */
+    public void addUnsafe(E e)
+    {
+        if (!enqueue(e))
+            throw new IllegalStateException("Full");
+    }
+
+    /* ------------------------------------------------------------ */
+    public E element()
+    {
+        synchronized (_lock)
+        {
+            if (isEmpty())
+                throw new NoSuchElementException();
+            return at(_nextE);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private E at(int index)
+    {
+        return (E)_elements[index];
+    }
+
+    /* ------------------------------------------------------------ */
+    public E peek()
+    {
+        synchronized (_lock)
+        {
+            if (isEmpty())
+                return null;
+            return at(_nextE);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public E poll()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                return null;
+            return dequeue();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private E dequeue()
+    {
+        E e = at(_nextE);
+        _elements[_nextE] = null;
+        _size--;
+        if (++_nextE == _elements.length)
+            _nextE = 0;
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    public E remove()
+    {
+        synchronized (_lock)
+        {
+            if (_size == 0)
+                throw new NoSuchElementException();
+            return dequeue();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clear()
+    {
+        synchronized (_lock)
+        {
+            _size = 0;
+            _nextE = 0;
+            _nextSlot = 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isEmpty()
+    {
+        synchronized (_lock)
+        {
+            return _size == 0;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int size()
+    {
+        synchronized (_lock)
+        {
+            return _size;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E get(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+            return getUnsafe(index);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get without synchronization or bounds checking.
+     *
+     * @param  index index of the element to return
+     * @return the element at the specified index
+     * @see #get(int)
+     */
+    public E getUnsafe(int index)
+    {
+        int i = (_nextE + index) % _elements.length;
+        return at(i);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E remove(int index)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = (_nextE + index) % _elements.length;
+            E old = at(i);
+
+            if (i < _nextSlot)
+            {
+                // 0                         _elements.length
+                //       _nextE........._nextSlot
+                System.arraycopy(_elements, i + 1, _elements, i, _nextSlot - i);
+                _nextSlot--;
+                _size--;
+            }
+            else
+            {
+                // 0                         _elements.length
+                // ......_nextSlot   _nextE..........
+                System.arraycopy(_elements, i + 1, _elements, i, _elements.length - i - 1);
+                if (_nextSlot > 0)
+                {
+                    _elements[_elements.length - 1] = _elements[0];
+                    System.arraycopy(_elements, 1, _elements, 0, _nextSlot - 1);
+                    _nextSlot--;
+                }
+                else
+                    _nextSlot = _elements.length - 1;
+
+                _size--;
+            }
+
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E set(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index >= _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            int i = _nextE + index;
+            if (i >= _elements.length)
+                i -= _elements.length;
+            E old = at(i);
+            _elements[i] = element;
+            return old;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void add(int index, E element)
+    {
+        synchronized (_lock)
+        {
+            if (index < 0 || index > _size)
+                throw new IndexOutOfBoundsException("!(" + 0 + "<" + index + "<=" + _size + ")");
+
+            if (_size == _elements.length && !grow())
+                throw new IllegalStateException("Full");
+
+            if (index == _size)
+            {
+                add(element);
+            }
+            else
+            {
+                int i = _nextE + index;
+                if (i >= _elements.length)
+                    i -= _elements.length;
+
+                _size++;
+                _nextSlot++;
+                if (_nextSlot == _elements.length)
+                    _nextSlot = 0;
+
+                if (i < _nextSlot)
+                {
+                    // 0                         _elements.length
+                    //       _nextE.....i..._nextSlot
+                    // 0                         _elements.length
+                    // ..i..._nextSlot   _nextE..........
+                    System.arraycopy(_elements, i, _elements, i + 1, _nextSlot - i);
+                    _elements[i] = element;
+                }
+                else
+                {
+                    // 0                         _elements.length
+                    // ......_nextSlot   _nextE.....i....
+                    if (_nextSlot > 0)
+                    {
+                        System.arraycopy(_elements, 0, _elements, 1, _nextSlot);
+                        _elements[0] = _elements[_elements.length - 1];
+                    }
+
+                    System.arraycopy(_elements, i, _elements, i + 1, _elements.length - i - 1);
+                    _elements[i] = element;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean grow()
+    {
+        synchronized (_lock)
+        {
+            if (_growCapacity <= 0)
+                return false;
+
+            Object[] elements = new Object[_elements.length + _growCapacity];
+
+            int split = _elements.length - _nextE;
+            if (split > 0)
+                System.arraycopy(_elements, _nextE, elements, 0, split);
+            if (_nextE != 0)
+                System.arraycopy(_elements, 0, elements, split, _nextSlot);
+
+            _elements = elements;
+            _nextE = 0;
+            _nextSlot = _size;
+            return true;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Atomics.java b/src/java/org/eclipse/jetty/util/Atomics.java
new file mode 100644
index 0000000..42fb489
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Atomics.java
@@ -0,0 +1,73 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Atomics
+{
+    private Atomics()
+    {
+    }
+
+    public static void updateMin(AtomicLong currentMin, long newValue)
+    {
+        long oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicLong currentMax, long newValue)
+    {
+        long oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+
+    public static void updateMin(AtomicInteger currentMin, int newValue)
+    {
+        int oldValue = currentMin.get();
+        while (newValue < oldValue)
+        {
+            if (currentMin.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMin.get();
+        }
+    }
+
+    public static void updateMax(AtomicInteger currentMax, int newValue)
+    {
+        int oldValue = currentMax.get();
+        while (newValue > oldValue)
+        {
+            if (currentMax.compareAndSet(oldValue, newValue))
+                break;
+            oldValue = currentMax.get();
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Attributes.java b/src/java/org/eclipse/jetty/util/Attributes.java
new file mode 100644
index 0000000..c65abc2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Attributes.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.Enumeration;
+
+/* ------------------------------------------------------------ */
+/** Attributes.
+ * Interface commonly used for storing attributes.
+ * 
+ *
+ */
+public interface Attributes
+{
+    public void removeAttribute(String name);
+    public void setAttribute(String name, Object attribute);
+    public Object getAttribute(String name);
+    public Enumeration<String> getAttributeNames();
+    public void clearAttributes();
+}
diff --git a/src/java/org/eclipse/jetty/util/AttributesMap.java b/src/java/org/eclipse/jetty/util/AttributesMap.java
new file mode 100644
index 0000000..e3b55af
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/AttributesMap.java
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** AttributesMap.
+ * 
+ *
+ */
+public class AttributesMap implements Attributes
+{
+    protected final Map<String,Object> _map;
+
+    /* ------------------------------------------------------------ */
+    public AttributesMap()
+    {
+        _map=new HashMap<String,Object>();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public AttributesMap(Map<String,Object> map)
+    {
+        _map=map;
+    }
+
+    /* ------------------------------------------------------------ */
+    public AttributesMap(AttributesMap map)
+    {
+        _map=new HashMap<String,Object>(map._map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#removeAttribute(java.lang.String)
+     */
+    public void removeAttribute(String name)
+    {
+        _map.remove(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#setAttribute(java.lang.String, java.lang.Object)
+     */
+    public void setAttribute(String name, Object attribute)
+    {
+        if (attribute==null)
+            _map.remove(name);
+        else
+            _map.put(name, attribute);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#getAttribute(java.lang.String)
+     */
+    public Object getAttribute(String name)
+    {
+        return _map.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
+     */
+    public Enumeration<String> getAttributeNames()
+    {
+        return Collections.enumeration(_map.keySet());
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
+     */
+    public Set<String> getAttributeNameSet()
+    {
+        return _map.keySet();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<Map.Entry<String, Object>> getAttributeEntrySet()
+    {
+        return _map.entrySet();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#getAttributeNames()
+     */
+    public static Enumeration<String> getAttributeNamesCopy(Attributes attrs)
+    {
+        if (attrs instanceof AttributesMap)
+            return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
+        
+        List<String> names = new ArrayList<String>();
+        names.addAll(Collections.list(attrs.getAttributeNames()));
+        return Collections.enumeration(names);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* 
+     * @see org.eclipse.jetty.util.Attributes#clear()
+     */
+    public void clearAttributes()
+    {
+        _map.clear();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return _map.size();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _map.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Set<String> keySet()
+    {
+        return _map.keySet();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void addAll(Attributes attributes)
+    {
+        Enumeration<String> e = attributes.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String name=e.nextElement();
+            setAttribute(name,attributes.getAttribute(name));
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/B64Code.java b/src/java/org/eclipse/jetty/util/B64Code.java
new file mode 100644
index 0000000..684c008
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/B64Code.java
@@ -0,0 +1,450 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+
+/* ------------------------------------------------------------ */
+/** Fast B64 Encoder/Decoder as described in RFC 1421.
+ * <p>Does not insert or interpret whitespace as described in RFC
+ * 1521. If you require this you must pre/post process your data.
+ * <p> Note that in a web context the usual case is to not want
+ * linebreaks or other white space in the encoded output.
+ * 
+ */
+public class B64Code
+{
+    // ------------------------------------------------------------------
+    static final char __pad='=';
+    static final char[] __rfc1421alphabet=
+            {
+                'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+                'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+                'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+                'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
+            };
+
+    static final byte[] __rfc1421nibbles;
+
+    static
+    {
+        __rfc1421nibbles=new byte[256];
+        for (int i=0;i<256;i++)
+            __rfc1421nibbles[i]=-1;
+        for (byte b=0;b<64;b++)
+            __rfc1421nibbles[(byte)__rfc1421alphabet[b]]=b;
+        __rfc1421nibbles[(byte)__pad]=0;
+    }
+
+    // ------------------------------------------------------------------
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @return String containing the encoded form of the input.
+     */
+    static public String encode(String s)
+    {
+        try
+        {
+            return encode(s,null);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalArgumentException(e.toString());
+        }
+    }
+
+    // ------------------------------------------------------------------
+    /**
+     * Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * @param s String to encode.
+     * @param charEncoding String representing the name of
+     *        the character encoding of the provided input String.
+     * @return String containing the encoded form of the input.
+     */
+    static public String encode(String s,String charEncoding)
+            throws UnsupportedEncodingException
+    {
+        byte[] bytes;
+        if (charEncoding==null)
+            bytes=s.getBytes(StringUtil.__ISO_8859_1);
+        else
+            bytes=s.getBytes(charEncoding);
+
+        return new String(encode(bytes));
+    }
+    
+    // ------------------------------------------------------------------
+    /**
+     * Fast Base 64 encode as described in RFC 1421.
+     * <p>Does not insert whitespace as described in RFC 1521.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @return char array containing the encoded form of the input.
+     */
+    static public char[] encode(byte[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&077];
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        return c;
+    }
+    
+    // ------------------------------------------------------------------
+    /**
+     * Fast Base 64 encode as described in RFC 1421 and RFC2045
+     * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
+     * <p> Avoids creating extra copies of the input/output.
+     * @param b byte array to encode.
+     * @param rfc2045 If true, break lines at 76 characters with CRLF
+     * @return char array containing the encoded form of the input.
+     */
+    static public char[] encode(byte[] b, boolean rfc2045)
+    {
+        if (b==null)
+            return null;
+        if (!rfc2045)
+            return encode(b);
+
+        int bLen=b.length;
+        int cLen=((bLen+2)/3)*4;
+        cLen+=2+2*(cLen/76);
+        char c[]=new char[cLen];
+        int ci=0;
+        int bi=0;
+        byte b0, b1, b2;
+        int stop=(bLen/3)*3;
+        int l=0;
+        while (bi<stop)
+        {
+            b0=b[bi++];
+            b1=b[bi++];
+            b2=b[bi++];
+            c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+            c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+            c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f|(b2>>>6)&0x03];
+            c[ci++]=__rfc1421alphabet[b2&077];
+            l+=4;
+            if (l%76==0)
+            {
+                c[ci++]=13;
+                c[ci++]=10;
+            }
+        }
+
+        if (bLen!=bi)
+        {
+            switch (bLen%3)
+            {
+                case 2:
+                    b0=b[bi++];
+                    b1=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f|(b1>>>4)&0x0f];
+                    c[ci++]=__rfc1421alphabet[(b1<<2)&0x3f];
+                    c[ci++]=__pad;
+                    break;
+
+                case 1:
+                    b0=b[bi++];
+                    c[ci++]=__rfc1421alphabet[(b0>>>2)&0x3f];
+                    c[ci++]=__rfc1421alphabet[(b0<<4)&0x3f];
+                    c[ci++]=__pad;
+                    c[ci++]=__pad;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        c[ci++]=13;
+        c[ci++]=10;
+        return c;
+    }
+
+    // ------------------------------------------------------------------
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param charEncoding String representing the character encoding
+     *        used to map the decoded bytes into a String.
+     * @return String decoded byte array.
+     * @throws UnsupportedEncodingException if the encoding is not supported
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public String decode(String encoded,String charEncoding)
+            throws UnsupportedEncodingException
+    {
+        byte[] decoded=decode(encoded);
+        if (charEncoding==null)
+            return new String(decoded);
+        return new String(decoded,charEncoding);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Fast Base 64 decode as described in RFC 1421.
+     * 
+     * <p>Unlike other decode methods, this does not attempt to 
+     * cope with extra whitespace as described in RFC 1521/2045.
+     * <p> Avoids creating extra copies of the input/output.
+     * <p> Note this code has been flattened for performance.
+     * @param b char array to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public byte[] decode(char[] b)
+    {
+        if (b==null)
+            return null;
+
+        int bLen=b.length;
+        if (bLen%4!=0)
+            throw new IllegalArgumentException("Input block size is not 4");
+
+        int li=bLen-1;
+        while (li>=0 && b[li]==(byte)__pad)
+            li--;
+
+        if (li<0)
+            return new byte[0];
+
+        // Create result array of exact required size.
+        int rLen=((li+1)*3)/4;
+        byte r[]=new byte[rLen];
+        int ri=0;
+        int bi=0;
+        int stop=(rLen/3)*3;
+        byte b0,b1,b2,b3;
+        try
+        {
+            while (ri<stop)
+            {
+                b0=__rfc1421nibbles[b[bi++]];
+                b1=__rfc1421nibbles[b[bi++]];
+                b2=__rfc1421nibbles[b[bi++]];
+                b3=__rfc1421nibbles[b[bi++]];
+                if (b0<0 || b1<0 || b2<0 || b3<0)
+                    throw new IllegalArgumentException("Not B64 encoded");
+
+                r[ri++]=(byte)(b0<<2|b1>>>4);
+                r[ri++]=(byte)(b1<<4|b2>>>2);
+                r[ri++]=(byte)(b2<<6|b3);
+            }
+
+            if (rLen!=ri)
+            {
+                switch (rLen%3)
+                {
+                    case 2:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        b2=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0 || b2<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        r[ri++]=(byte)(b1<<4|b2>>>2);
+                        break;
+
+                    case 1:
+                        b0=__rfc1421nibbles[b[bi++]];
+                        b1=__rfc1421nibbles[b[bi++]];
+                        if (b0<0 || b1<0)
+                            throw new IllegalArgumentException("Not B64 encoded");
+                        r[ri++]=(byte)(b0<<2|b1>>>4);
+                        break;
+
+                    default:
+                        break;
+                }
+            }
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+            throw new IllegalArgumentException("char "+bi
+                    +" was not B64 encoded");
+        }
+
+        return r;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public byte[] decode(String encoded)
+    {
+        if (encoded==null)
+            return null;
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream(4*encoded.length()/3);        
+        decode(encoded, bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Base 64 decode as described in RFC 2045.
+     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
+     * @param encoded String to decode.
+     * @param output stream for decoded bytes
+     * @return byte array containing the decoded form of the input.
+     * @throws IllegalArgumentException if the input is not a valid
+     *         B64 encoding.
+     */
+    static public void decode (String encoded, ByteArrayOutputStream bout)
+    {
+        if (encoded==null)
+            return;
+        
+        if (bout == null)
+            throw new IllegalArgumentException("No outputstream for decoded bytes");
+        
+        int ci=0;
+        byte nibbles[] = new byte[4];
+        int s=0;
+  
+        while (ci<encoded.length())
+        {
+            char c=encoded.charAt(ci++);
+
+            if (c==__pad)
+                break;
+            
+            if (Character.isWhitespace(c))
+                continue;
+
+            byte nibble=__rfc1421nibbles[c];
+            if (nibble<0)
+                throw new IllegalArgumentException("Not B64 encoded");
+
+            nibbles[s++]=__rfc1421nibbles[c];
+
+            switch(s)
+            {
+                case 1:
+                    break;
+                case 2:
+                    bout.write(nibbles[0]<<2|nibbles[1]>>>4);
+                    break;
+                case 3:
+                    bout.write(nibbles[1]<<4|nibbles[2]>>>2);
+                    break;
+                case 4:
+                    bout.write(nibbles[2]<<6|nibbles[3]);
+                    s=0;
+                    break;
+            }
+
+        }
+
+        return;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static void encode(int value,Appendable buf) throws IOException
+    {
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4)]);
+        buf.append('=');
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void encode(long lvalue,Appendable buf) throws IOException
+    {
+        int value=(int)(0xFFFFFFFC&(lvalue>>32));
+        buf.append(__rfc1421alphabet[0x3f&((0xFC000000&value)>>26)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x03F00000&value)>>20)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000FC000&value)>>14)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x00003F00&value)>>8)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000000FC&value)>>2)]);
+        
+        buf.append(__rfc1421alphabet[0x3f&((0x00000003&value)<<4) + (0xf&(int)(lvalue>>28))]);
+        
+        value=0x0FFFFFFF&(int)lvalue;
+        buf.append(__rfc1421alphabet[0x3f&((0x0FC00000&value)>>22)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x003F0000&value)>>16)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000FC00&value)>>10)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x000003F0&value)>>4)]);
+        buf.append(__rfc1421alphabet[0x3f&((0x0000000F&value)<<2)]);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/BlockingArrayQueue.java b/src/java/org/eclipse/jetty/util/BlockingArrayQueue.java
new file mode 100644
index 0000000..80e3c6b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/BlockingArrayQueue.java
@@ -0,0 +1,704 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+/* ------------------------------------------------------------ */
+/** Queue backed by a circular array.
+ * 
+ * This queue is uses  a variant of the two lock queue algorithm to
+ * provide an efficient queue or list backed by a growable circular
+ * array.  This queue also has a partial implementation of 
+ * {@link java.util.concurrent.BlockingQueue}, specifically the {@link #take()} and 
+ * {@link #poll(long, TimeUnit)} methods.  
+ * Unlike {@link java.util.concurrent.ArrayBlockingQueue}, this class is
+ * able to grow and provides a blocking put call.
+ * <p>
+ * The queue has both a capacity (the size of the array currently allocated)
+ * and a limit (the maximum size that may be allocated), which defaults to 
+ * {@link Integer#MAX_VALUE}.
+ * 
+ * @param <E> The element type
+ */
+public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQueue<E>
+{
+    public final int DEFAULT_CAPACITY=128;
+    public final int DEFAULT_GROWTH=64;
+    private final int _limit;
+    private final AtomicInteger _size=new AtomicInteger();
+    private final int _growCapacity;
+    
+    private volatile int _capacity;
+    private Object[] _elements;
+    
+    private final ReentrantLock _headLock = new ReentrantLock();
+    private final Condition _notEmpty = _headLock.newCondition();
+    private int _head;
+
+    // spacers created to prevent false sharing between head and tail http://en.wikipedia.org/wiki/False_sharing
+    // TODO verify this has benefits
+    private long _space0;
+    private long _space1;
+    private long _space2;
+    private long _space3;
+    private long _space4;
+    private long _space5;
+    private long _space6;
+    private long _space7;
+    
+    private final ReentrantLock _tailLock = new ReentrantLock();
+    private int _tail;
+    
+
+    /* ------------------------------------------------------------ */
+    /** Create a growing partially blocking Queue
+     * 
+     */
+    public BlockingArrayQueue()
+    {
+        _elements=new Object[DEFAULT_CAPACITY];
+        _growCapacity=DEFAULT_GROWTH;
+        _capacity=_elements.length;
+        _limit=Integer.MAX_VALUE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a fixed size partially blocking Queue
+     * @param limit The initial capacity and the limit.
+     */
+    public BlockingArrayQueue(int limit)
+    {
+        _elements=new Object[limit];
+        _capacity=_elements.length;
+        _growCapacity=-1;
+        _limit=limit;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a growing partially blocking Queue.
+     * @param capacity Initial capacity
+     * @param growBy Incremental capacity.
+     */
+    public BlockingArrayQueue(int capacity,int growBy)
+    {
+        _elements=new Object[capacity];
+        _capacity=_elements.length;
+        _growCapacity=growBy;
+        _limit=Integer.MAX_VALUE;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Create a growing limited partially blocking Queue.
+     * @param capacity Initial capacity
+     * @param growBy Incremental capacity.
+     * @param limit maximum capacity.
+     */
+    public BlockingArrayQueue(int capacity,int growBy,int limit)
+    {
+        if (capacity>limit)
+            throw new IllegalArgumentException();
+        
+        _elements=new Object[capacity];
+        _capacity=_elements.length;
+        _growCapacity=growBy;
+        _limit=limit;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getCapacity()
+    {
+        return _capacity;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getLimit()
+    {
+        return _limit;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean add(E e)
+    {
+        return offer(e);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public E element()
+    {
+        E e = peek();
+        if (e==null)
+            throw new NoSuchElementException();
+        return e;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public E peek()
+    {
+        if (_size.get() == 0)
+            return null;
+        
+        E e = null;
+        _headLock.lock(); // Size cannot shrink
+        try 
+        {
+            if (_size.get() > 0) 
+                e = (E)_elements[_head];
+        } 
+        finally 
+        {
+            _headLock.unlock();
+        }
+        
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean offer(E e)
+    {
+        if (e == null) 
+            throw new NullPointerException();
+        
+        boolean not_empty=false;
+        _tailLock.lock();  // size cannot grow... only shrink
+        try 
+        {
+            if (_size.get() >= _limit) 
+                return false;
+            
+            // should we expand array?
+            if (_size.get()==_capacity)
+            {
+                _headLock.lock();   // Need to grow array
+                try
+                {
+                    if (!grow())
+                        return false;
+                }
+                finally
+                {
+                    _headLock.unlock();
+                }
+            }
+
+            // add the element
+            _elements[_tail]=e;
+            _tail=(_tail+1)%_capacity;
+
+            not_empty=0==_size.getAndIncrement();
+            
+        } 
+        finally 
+        {
+            _tailLock.unlock();
+        }
+        
+        if (not_empty)
+        {
+            _headLock.lock();
+            try
+            {
+                _notEmpty.signal();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }  
+
+        return true;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public E poll()
+    {
+        if (_size.get() == 0)
+            return null;
+        
+        E e = null;
+        _headLock.lock(); // Size cannot shrink
+        try 
+        {
+            if (_size.get() > 0) 
+            {
+                final int head=_head;
+                e = (E)_elements[head];
+                _elements[head]=null;
+                _head=(head+1)%_capacity;
+                
+                if (_size.decrementAndGet()>0)
+                    _notEmpty.signal();
+            }
+        } 
+        finally 
+        {
+            _headLock.unlock();
+        }
+        
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieves and removes the head of this queue, waiting
+     * if no elements are present on this queue.
+     * @return the head of this queue
+     * @throws InterruptedException if interrupted while waiting.
+     */
+    @SuppressWarnings("unchecked")
+    public E take() throws InterruptedException
+    {
+        E e = null;
+        _headLock.lockInterruptibly();  // Size cannot shrink
+        try 
+        {
+            try 
+            {
+                while (_size.get() == 0)
+                {
+                    _notEmpty.await();
+                }
+            } 
+            catch (InterruptedException ie) 
+            {
+                _notEmpty.signal();
+                throw ie;
+            }
+
+            final int head=_head;
+            e = (E)_elements[head];
+            _elements[head]=null;
+            _head=(head+1)%_capacity;
+
+            if (_size.decrementAndGet()>0)
+                _notEmpty.signal();
+        } 
+        finally 
+        {
+            _headLock.unlock();
+        }
+        
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieves and removes the head of this queue, waiting
+     * if necessary up to the specified wait time if no elements are
+     * present on this queue.
+     * @param time how long to wait before giving up, in units of
+     * <tt>unit</tt>
+     * @param unit a <tt>TimeUnit</tt> determining how to interpret the
+     * <tt>timeout</tt> parameter
+     * @return the head of this queue, or <tt>null</tt> if the
+     * specified waiting time elapses before an element is present.
+     * @throws InterruptedException if interrupted while waiting.
+     */
+    @SuppressWarnings("unchecked")
+    public E poll(long time, TimeUnit unit) throws InterruptedException
+    {
+        
+        E e = null;
+
+        long nanos = unit.toNanos(time);
+        
+        _headLock.lockInterruptibly(); // Size cannot shrink
+        try 
+        {    
+            try 
+            {
+                while (_size.get() == 0)
+                {
+                    if (nanos<=0)
+                        return null;
+                    nanos = _notEmpty.awaitNanos(nanos);
+                }
+            } 
+            catch (InterruptedException ie) 
+            {
+                _notEmpty.signal();
+                throw ie;
+            }
+
+            e = (E)_elements[_head];
+            _elements[_head]=null;
+            _head=(_head+1)%_capacity;
+
+            if (_size.decrementAndGet()>0)
+                _notEmpty.signal();
+        } 
+        finally 
+        {
+            _headLock.unlock();
+        }
+        
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    public E remove()
+    {
+        E e=poll();
+        if (e==null)
+            throw new NoSuchElementException();
+        return e;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clear()
+    {
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+                _head=0;
+                _tail=0;
+                _size.set(0);
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isEmpty()
+    {
+        return _size.get()==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int size()
+    {
+        return _size.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    @Override
+    public E get(int index)
+    {
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+                if (index<0 || index>=_size.get())
+                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+                int i = _head+index;
+                if (i>=_capacity)
+                    i-=_capacity;
+                return (E)_elements[i];
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public E remove(int index)
+    {
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+
+                if (index<0 || index>=_size.get())
+                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+
+                int i = _head+index;
+                if (i>=_capacity)
+                    i-=_capacity;
+                @SuppressWarnings("unchecked")
+                E old=(E)_elements[i];
+
+                if (i<_tail)
+                {
+                    System.arraycopy(_elements,i+1,_elements,i,_tail-i);
+                    _tail--;
+                    _size.decrementAndGet();
+                }
+                else
+                {
+                    System.arraycopy(_elements,i+1,_elements,i,_capacity-i-1);
+                    if (_tail>0)
+                    {
+                        _elements[_capacity]=_elements[0];
+                        System.arraycopy(_elements,1,_elements,0,_tail-1);
+                        _tail--;
+                    }
+                    else
+                        _tail=_capacity-1;
+
+                    _size.decrementAndGet();
+                }
+
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public E set(int index, E e)
+    {
+        if (e == null) 
+            throw new NullPointerException();
+
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+
+                if (index<0 || index>=_size.get())
+                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+
+                int i = _head+index;
+                if (i>=_capacity)
+                    i-=_capacity;
+                @SuppressWarnings("unchecked")
+                E old=(E)_elements[i];
+                _elements[i]=e;
+                return old;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void add(int index, E e)
+    {
+        if (e == null) 
+            throw new NullPointerException();
+
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+
+                if (index<0 || index>_size.get())
+                    throw new IndexOutOfBoundsException("!("+0+"<"+index+"<="+_size+")");
+
+                if (index==_size.get())
+                {
+                    add(e);
+                }
+                else
+                {
+                    if (_tail==_head)
+                        if (!grow())
+                            throw new IllegalStateException("full");
+
+                    int i = _head+index;
+                    if (i>=_capacity)
+                        i-=_capacity;
+
+                    _size.incrementAndGet();
+                    _tail=(_tail+1)%_capacity;
+
+
+                    if (i<_tail)
+                    {
+                        System.arraycopy(_elements,i,_elements,i+1,_tail-i);
+                        _elements[i]=e;
+                    }
+                    else
+                    {
+                        if (_tail>0)
+                        {
+                            System.arraycopy(_elements,0,_elements,1,_tail);
+                            _elements[0]=_elements[_capacity-1];
+                        }
+
+                        System.arraycopy(_elements,i,_elements,i+1,_capacity-i-1);
+                        _elements[i]=e;
+                    }
+                }
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean grow()
+    {
+        if (_growCapacity<=0)
+            return false;
+
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+                final int head=_head;
+                final int tail=_tail;
+                final int new_tail;
+
+                Object[] elements=new Object[_capacity+_growCapacity];
+
+                if (head<tail)
+                {
+                    new_tail=tail-head;
+                    System.arraycopy(_elements,head,elements,0,new_tail);
+                }
+                else if (head>tail || _size.get()>0)
+                {
+                    new_tail=_capacity+tail-head;
+                    int cut=_capacity-head;
+                    System.arraycopy(_elements,head,elements,0,cut);
+                    System.arraycopy(_elements,0,elements,cut,tail);
+                }
+                else
+                {
+                    new_tail=0;
+                }
+
+                _elements=elements;
+                _capacity=_elements.length;
+                _head=0;
+                _tail=new_tail; 
+                return true;
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public int drainTo(Collection<? super E> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public int drainTo(Collection<? super E> c, int maxElements)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean offer(E o, long timeout, TimeUnit unit) throws InterruptedException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void put(E o) throws InterruptedException
+    {
+        if (!add(o))
+            throw new IllegalStateException("full");
+    }
+
+    /* ------------------------------------------------------------ */
+    public int remainingCapacity()
+    {
+        _tailLock.lock();
+        try
+        {
+            _headLock.lock();
+            try
+            {
+                return getCapacity()-size();
+            }
+            finally
+            {
+                _headLock.unlock();
+            }
+        }
+        finally
+        {
+            _tailLock.unlock();
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    long sumOfSpace()
+    {
+        // this method exists to stop clever optimisers removing the spacers
+        return _space0++ +_space1++ +_space2++ +_space3++ +_space4++ +_space5++ +_space6++ +_space7++; 
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java b/src/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
new file mode 100644
index 0000000..6085d30
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java
@@ -0,0 +1,272 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+
+/* ------------------------------------------------------------ */
+/** Byte Array ISO 8859 writer. 
+ * This class combines the features of a OutputStreamWriter for
+ * ISO8859 encoding with that of a ByteArrayOutputStream.  It avoids
+ * many inefficiencies associated with these standard library classes.
+ * It has been optimized for standard ASCII characters.
+ * 
+ * 
+ */
+public class ByteArrayISO8859Writer extends Writer
+{
+    private byte[] _buf;
+    private int _size;
+    private ByteArrayOutputStream2 _bout=null;
+    private OutputStreamWriter _writer=null;
+    private boolean _fixed=false;
+
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     */
+    public ByteArrayISO8859Writer()
+    {
+        _buf=new byte[2048];
+    } 
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param capacity Buffer capacity
+     */
+    public ByteArrayISO8859Writer(int capacity)
+    {
+        _buf=new byte[capacity];
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ByteArrayISO8859Writer(byte[] buf)
+    {
+        _buf=buf;
+        _fixed=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object getLock()
+    {
+        return lock;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return _size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int capacity()
+    {
+        return _buf.length;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int spareCapacity()
+    {
+        return _buf.length-_size;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setLength(int l)
+    {
+        _size=l;
+    }
+
+    /* ------------------------------------------------------------ */
+    public byte[] getBuf()
+    {
+        return _buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void writeTo(OutputStream out)
+        throws IOException
+    {
+        out.write(_buf,0,_size);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void write(char c)
+        throws IOException
+    {
+        ensureSpareCapacity(1);
+        if (c>=0&&c<=0x7f)
+            _buf[_size++]=(byte)c;
+        else
+        {
+            char[] ca ={c};
+            writeEncoded(ca,0,1);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca)
+        throws IOException
+    {
+        ensureSpareCapacity(ca.length);
+        for (int i=0;i<ca.length;i++)
+        {
+            char c=ca[i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,i,ca.length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(char[] ca,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=ca[offset+i];
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(ca,offset+i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s)
+        throws IOException
+    {
+        if (s==null)
+        {
+            write("null",0,4);
+            return;
+        }
+        
+        int length=s.length();
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(i);
+            if (c>=0x0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),i,length-i);
+                break;
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(String s,int offset, int length)
+        throws IOException
+    {
+        ensureSpareCapacity(length);
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+            if (c>=0&&c<=0x7f)
+                _buf[_size++]=(byte)c;
+            else
+            {
+                writeEncoded(s.toCharArray(),offset+i,length-i);
+                break;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void writeEncoded(char[] ca,int offset, int length)
+        throws IOException
+    {
+        if (_bout==null)
+        {
+            _bout = new ByteArrayOutputStream2(2*length);
+            _writer = new OutputStreamWriter(_bout,StringUtil.__ISO_8859_1);
+        }
+        else
+            _bout.reset();
+        _writer.write(ca,offset,length);
+        _writer.flush();
+        ensureSpareCapacity(_bout.getCount());
+        System.arraycopy(_bout.getBuf(),0,_buf,_size,_bout.getCount());
+        _size+=_bout.getCount();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void flush()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void resetWriter()
+    {
+        _size=0;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void close()
+    {}
+
+    /* ------------------------------------------------------------ */
+    public void destroy()
+    {
+        _buf=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void ensureSpareCapacity(int n)
+        throws IOException
+    {
+        if (_size+n>_buf.length)
+        {
+            if (_fixed)
+                throw new IOException("Buffer overflow: "+_buf.length);
+            byte[] buf = new byte[(_buf.length+n)*4/3];
+            System.arraycopy(_buf,0,buf,0,_size);
+            _buf=buf;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public byte[] getByteArray()
+    {
+        byte[] data=new byte[_size];
+        System.arraycopy(_buf,0,data,0,_size);
+        return data;
+    }
+    
+}
+    
+    
diff --git a/src/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java b/src/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java
new file mode 100644
index 0000000..1d10fbc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ByteArrayOutputStream2.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+
+/* ------------------------------------------------------------ */
+/** ByteArrayOutputStream with public internals
+
+ * 
+ */
+public class ByteArrayOutputStream2 extends ByteArrayOutputStream
+{
+    public ByteArrayOutputStream2(){super();}
+    public ByteArrayOutputStream2(int size){super(size);}
+    public byte[] getBuf(){return buf;}
+    public int getCount(){return count;}
+    public void setCount(int count){this.count = count;}
+
+    public void reset(int minSize)
+    {
+        reset();
+        if (buf.length<minSize)
+        {
+            buf=new byte[minSize];
+        }
+    }
+    
+    public void writeUnchecked(int b)
+    {
+        buf[count++]=(byte)b;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/util/ConcurrentHashSet.java b/src/java/org/eclipse/jetty/util/ConcurrentHashSet.java
new file mode 100644
index 0000000..4a4c8e6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ConcurrentHashSet.java
@@ -0,0 +1,126 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConcurrentHashSet<E> extends AbstractSet<E> implements Set<E>
+{
+    private final Map<E, Boolean> _map = new ConcurrentHashMap<E, Boolean>();
+    private transient Set<E> _keys = _map.keySet();
+
+    public ConcurrentHashSet()
+    {
+    }
+
+    @Override
+    public boolean add(E e)
+    {
+        return _map.put(e,Boolean.TRUE) == null;
+    }
+
+    @Override
+    public void clear()
+    {
+        _map.clear();
+    }
+
+    @Override
+    public boolean contains(Object o)
+    {
+        return _map.containsKey(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c)
+    {
+        return _keys.containsAll(c);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return o == this || _keys.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return _keys.hashCode();
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return _map.isEmpty();
+    }
+
+    @Override
+    public Iterator<E> iterator()
+    {
+        return _keys.iterator();
+    }
+
+    @Override
+    public boolean remove(Object o)
+    {
+        return _map.remove(o) != null;
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c)
+    {
+        return _keys.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c)
+    {
+        return _keys.retainAll(c);
+    }
+
+    @Override
+    public int size()
+    {
+        return _map.size();
+    }
+
+    @Override
+    public Object[] toArray()
+    {
+        return _keys.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a)
+    {
+        return _keys.toArray(a);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _keys.toString();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/DateCache.java b/src/java/org/eclipse/jetty/util/DateCache.java
new file mode 100644
index 0000000..42df49b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/DateCache.java
@@ -0,0 +1,311 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/* ------------------------------------------------------------ */
+/**  Date Format Cache.
+ * Computes String representations of Dates and caches
+ * the results so that subsequent requests within the same minute
+ * will be fast.
+ *
+ * Only format strings that contain either "ss" or "ss.SSS" are
+ * handled.
+ *
+ * The timezone of the date may be included as an ID with the "zzz"
+ * format string or as an offset with the "ZZZ" format string.
+ *
+ * If consecutive calls are frequently very different, then this
+ * may be a little slower than a normal DateFormat.
+ *
+ * 
+ * 
+ */
+
+public class DateCache  
+{
+    public static String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
+    private static long __hitWindow=60*60;
+    
+    private String _formatString;
+    private String _tzFormatString;
+    private SimpleDateFormat _tzFormat;
+    
+    private String _minFormatString;
+    private SimpleDateFormat _minFormat;
+
+    private String _secFormatString;
+    private String _secFormatString0;
+    private String _secFormatString1;
+
+    private long _lastMinutes = -1;
+    private long _lastSeconds = -1;
+    private int _lastMs = -1;
+    private String _lastResult = null;
+
+    private Locale _locale	= null;
+    private DateFormatSymbols	_dfs	= null;
+
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use a default format. The default format
+     * generates the same results as Date.toString().
+     */
+    public DateCache()
+    {
+        this(DEFAULT_FORMAT);
+        getFormat().setTimeZone(TimeZone.getDefault());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     * Make a DateCache that will use the given format
+     */
+    public DateCache(String format)
+    {
+        _formatString=format;
+        setTimeZone(TimeZone.getDefault());
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,Locale l)
+    {
+        _formatString=format;
+        _locale = l;
+        setTimeZone(TimeZone.getDefault());       
+    }
+    
+    /* ------------------------------------------------------------ */
+    public DateCache(String format,DateFormatSymbols s)
+    {
+        _formatString=format;
+        _dfs = s;
+        setTimeZone(TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the timezone.
+     * @param tz TimeZone
+     */
+    public synchronized void setTimeZone(TimeZone tz)
+    {
+        setTzFormatString(tz);        
+        if( _locale != null ) 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
+            _minFormat=new SimpleDateFormat(_minFormatString,_locale);
+        }
+        else if( _dfs != null ) 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString,_dfs);
+            _minFormat=new SimpleDateFormat(_minFormatString,_dfs);
+        }
+        else 
+        {
+            _tzFormat=new SimpleDateFormat(_tzFormatString);
+            _minFormat=new SimpleDateFormat(_minFormatString);
+        }
+        _tzFormat.setTimeZone(tz);
+        _minFormat.setTimeZone(tz);
+        _lastSeconds=-1;
+        _lastMinutes=-1;        
+    }
+
+    /* ------------------------------------------------------------ */
+    public TimeZone getTimeZone()
+    {
+        return _tzFormat.getTimeZone();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set the timezone.
+     * @param timeZoneId TimeZoneId the ID of the zone as used by
+     * TimeZone.getTimeZone(id)
+     */
+    public void setTimeZoneID(String timeZoneId)
+    {
+        setTimeZone(TimeZone.getTimeZone(timeZoneId));
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void setTzFormatString(final  TimeZone tz )
+    {
+        int zIndex = _formatString.indexOf( "ZZZ" );
+        if( zIndex >= 0 )
+        {
+            String ss1 = _formatString.substring( 0, zIndex );
+            String ss2 = _formatString.substring( zIndex+3 );
+            int tzOffset = tz.getRawOffset();
+            
+            StringBuilder sb = new StringBuilder(_formatString.length()+10);
+            sb.append(ss1);
+            sb.append("'");
+            if( tzOffset >= 0 )
+                sb.append( '+' );
+            else
+            {
+                tzOffset = -tzOffset;
+                sb.append( '-' );
+            }
+            
+            int raw = tzOffset / (1000*60);		// Convert to seconds
+            int hr = raw / 60;
+            int min = raw % 60;
+            
+            if( hr < 10 )
+                sb.append( '0' );
+            sb.append( hr );
+            if( min < 10 )
+                sb.append( '0' );
+            sb.append( min );
+            sb.append( '\'' );
+            
+            sb.append(ss2);
+            _tzFormatString=sb.toString();            
+        }
+        else
+            _tzFormatString=_formatString;
+        setMinFormatString();
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    private void setMinFormatString()
+    {
+        int i = _tzFormatString.indexOf("ss.SSS");
+        int l = 6;
+        if (i>=0)
+            throw new IllegalStateException("ms not supported");
+        i = _tzFormatString.indexOf("ss");
+        l=2;
+        
+        // Build a formatter that formats a second format string
+        String ss1=_tzFormatString.substring(0,i);
+        String ss2=_tzFormatString.substring(i+l);
+        _minFormatString =ss1+"'ss'"+ss2;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public synchronized String format(Date inDate)
+    {
+        return format(inDate.getTime());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Format a date according to our stored formatter.
+     * @param inDate 
+     * @return Formatted date
+     */
+    public synchronized String format(long inDate)
+    {
+        long seconds = inDate / 1000;
+
+        // Is it not suitable to cache?
+        if (seconds<_lastSeconds ||
+            _lastSeconds>0 && seconds>_lastSeconds+__hitWindow)
+        {
+            // It's a cache miss
+            Date d = new Date(inDate);
+            return _tzFormat.format(d);
+            
+        }
+                                          
+        // Check if we are in the same second
+        // and don't care about millis
+        if (_lastSeconds==seconds )
+            return _lastResult;
+
+        Date d = new Date(inDate);
+        
+        // Check if we need a new format string
+        long minutes = seconds/60;
+        if (_lastMinutes != minutes)
+        {
+            _lastMinutes = minutes;
+            _secFormatString=_minFormat.format(d);
+
+            int i=_secFormatString.indexOf("ss");
+            int l=2;
+            _secFormatString0=_secFormatString.substring(0,i);
+            _secFormatString1=_secFormatString.substring(i+l);
+        }
+
+        // Always format if we get here
+        _lastSeconds = seconds;
+        StringBuilder sb=new StringBuilder(_secFormatString.length());
+        sb.append(_secFormatString0);
+        int s=(int)(seconds%60);
+        if (s<10)
+            sb.append('0');
+        sb.append(s);
+        sb.append(_secFormatString1);
+        _lastResult=sb.toString();
+
+                
+        return _lastResult;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Format to string buffer. 
+     * @param inDate Date the format
+     * @param buffer StringBuilder
+     */
+    public void format(long inDate, StringBuilder buffer)
+    {
+        buffer.append(format(inDate));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the format.
+     */
+    public SimpleDateFormat getFormat()
+    {
+        return _minFormat;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFormatString()
+    {
+        return _formatString;
+    }    
+
+    /* ------------------------------------------------------------ */
+    public String now()
+    {
+        long now=System.currentTimeMillis();
+        _lastMs=(int)(now%1000);
+        return format(now);
+    }
+
+    /* ------------------------------------------------------------ */
+    public int lastMs()
+    {
+        return _lastMs;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/HostMap.java b/src/java/org/eclipse/jetty/util/HostMap.java
new file mode 100644
index 0000000..23e10bb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/HostMap.java
@@ -0,0 +1,108 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/* ------------------------------------------------------------ */
+/**
+ */
+@SuppressWarnings("serial")
+public class HostMap<TYPE> extends HashMap<String, TYPE>
+{
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     */
+    public HostMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty HostMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public HostMap(int capacity)
+    {
+        super (capacity);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String host, TYPE object)
+        throws IllegalArgumentException
+    {
+        return super.put(host, object);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * hostname by taking into account the domain suffix matches.
+     * 
+     * @param host hostname
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String host)
+    {
+        if (host == null)
+            return LazyList.getList(super.entrySet());
+        
+        int idx = 0;
+        String domain = host.trim();
+        HashSet<String> domains = new HashSet<String>();
+        do {
+            domains.add(domain);
+            if ((idx = domain.indexOf('.')) > 0)
+            {
+                domain = domain.substring(idx+1);
+            }
+        } while (idx > 0);
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (domains.contains(entry.getKey()))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+       
+        return entries;        
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/IO.java b/src/java/org/eclipse/jetty/util/IO.java
new file mode 100644
index 0000000..bc253bc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/IO.java
@@ -0,0 +1,556 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+
+/* ======================================================================== */
+/** IO Utilities.
+ * Provides stream handling utilities in
+ * singleton Threadpool implementation accessed by static members.
+ */
+public class IO 
+{
+    private static final Logger LOG = Log.getLogger(IO.class);
+    
+    /* ------------------------------------------------------------------- */
+    public final static String
+        CRLF      = "\015\012";
+
+    /* ------------------------------------------------------------------- */
+    public final static byte[]
+        CRLF_BYTES    = {(byte)'\015',(byte)'\012'};
+
+    /* ------------------------------------------------------------------- */
+    public static int bufferSize = 64*1024;
+    
+    /* ------------------------------------------------------------------- */
+    // TODO get rid of this singleton!
+    private static class Singleton {
+        static final QueuedThreadPool __pool=new QueuedThreadPool();
+        static
+        {
+            try{__pool.start();}
+            catch(Exception e){LOG.warn(e); System.exit(1);}
+        }
+    }
+
+    /* ------------------------------------------------------------------- */
+    static class Job implements Runnable
+    {
+        InputStream in;
+        OutputStream out;
+        Reader read;
+        Writer write;
+
+        Job(InputStream in,OutputStream out)
+        {
+            this.in=in;
+            this.out=out;
+            this.read=null;
+            this.write=null;
+        }
+        Job(Reader read,Writer write)
+        {
+            this.in=null;
+            this.out=null;
+            this.read=read;
+            this.write=write;
+        }
+        
+        /* ------------------------------------------------------------ */
+        /* 
+         * @see java.lang.Runnable#run()
+         */
+        public void run()
+        {
+            try {
+                if (in!=null)
+                    copy(in,out,-1);
+                else
+                    copy(read,write,-1);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+                try{
+                    if (out!=null)
+                        out.close();
+                    if (write!=null)
+                        write.close();
+                }
+                catch(IOException e2)
+                {
+                    LOG.ignore(e2);
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream out until EOF or exception.
+     * in own thread
+     */
+    public static void copyThread(InputStream in, OutputStream out)
+    {
+        try{
+            Job job=new Job(in,out);
+            if (!Singleton.__pool.dispatch(job))
+                job.run();
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream out until EOF or exception.
+     */
+    public static void copy(InputStream in, OutputStream out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream out until EOF or exception
+     * in own thread
+     */
+    public static void copyThread(Reader in, Writer out)
+    {
+        try
+        {
+            Job job=new Job(in,out);
+            if (!Singleton.__pool.dispatch(job))
+                job.run();
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer out until EOF or exception.
+     */
+    public static void copy(Reader in, Writer out)
+         throws IOException
+    {
+        copy(in,out,-1);
+    }
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Stream in to Stream for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(InputStream in,
+                            OutputStream out,
+                            long byteCount)
+         throws IOException
+    {     
+        byte buffer[] = new byte[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                int max = byteCount<bufferSize?(int)byteCount:bufferSize;
+                len=in.read(buffer,0,max);
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len<0 )
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }  
+    
+    /* ------------------------------------------------------------------- */
+    /** Copy Reader to Writer for byteCount bytes or until EOF or exception.
+     */
+    public static void copy(Reader in,
+                            Writer out,
+                            long byteCount)
+         throws IOException
+    {  
+        char buffer[] = new char[bufferSize];
+        int len=bufferSize;
+        
+        if (byteCount>=0)
+        {
+            while (byteCount>0)
+            {
+                if (byteCount<bufferSize)
+                    len=in.read(buffer,0,(int)byteCount);
+                else
+                    len=in.read(buffer,0,bufferSize);                   
+                
+                if (len==-1)
+                    break;
+                
+                byteCount -= len;
+                out.write(buffer,0,len);
+            }
+        }
+        else if (out instanceof PrintWriter)
+        {
+            PrintWriter pout=(PrintWriter)out;
+            while (!pout.checkError())
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+        else
+        {
+            while (true)
+            {
+                len=in.read(buffer,0,bufferSize);
+                if (len==-1)
+                    break;
+                out.write(buffer,0,len);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Copy files or directories
+     * @param from
+     * @param to
+     * @throws IOException
+     */
+    public static void copy(File from,File to) throws IOException
+    {
+        if (from.isDirectory())
+            copyDir(from,to);
+        else
+            copyFile(from,to);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void copyDir(File from,File to) throws IOException
+    {
+        if (to.exists())
+        {
+            if (!to.isDirectory())
+                throw new IllegalArgumentException(to.toString());
+        }
+        else
+            to.mkdirs();
+        
+        File[] files = from.listFiles();
+        if (files!=null)
+        {
+            for (int i=0;i<files.length;i++)
+            {
+                String name = files[i].getName();
+                if (".".equals(name) || "..".equals(name))
+                    continue;
+                copy(files[i],new File(to,name));
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void copyFile(File from,File to) throws IOException
+    {
+        FileInputStream in=new FileInputStream(from);
+        FileOutputStream out=new FileOutputStream(to);
+        copy(in,out);
+        in.close();
+        out.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in)
+        throws IOException
+    {
+        return toString(in,null);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(InputStream in,String encoding)
+        throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        InputStreamReader reader = encoding==null?new InputStreamReader(in):new InputStreamReader(in,encoding);
+        
+        copy(reader,writer);
+        return writer.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Read input stream to string.
+     */
+    public static String toString(Reader in)
+        throws IOException
+    {
+        StringWriter writer=new StringWriter();
+        copy(in,writer);
+        return writer.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Delete File.
+     * This delete will recursively delete directories - BE CAREFULL
+     * @param file The file to be deleted.
+     */
+    public static boolean delete(File file)
+    {
+        if (!file.exists())
+            return false;
+        if (file.isDirectory())
+        {
+            File[] files = file.listFiles();
+            for (int i=0;files!=null && i<files.length;i++)
+                delete(files[i]);
+        }
+        return file.delete();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * closes any {@link Closeable}
+     *
+     * @param c the closeable to close
+     */
+    public static void close(Closeable c)
+    {
+        try
+        {
+            if (c != null)
+                c.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+    
+    /**
+     * closes an input stream, and logs exceptions
+     *
+     * @param is the input stream to close
+     */
+    public static void close(InputStream is)
+    {
+        try
+        {
+            if (is != null)
+                is.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a reader, and logs exceptions
+     * 
+     * @param reader the reader to close
+     */
+    public static void close(Reader reader)
+    {
+        try
+        {
+            if (reader != null)
+                reader.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /**
+     * closes a writer, and logs exceptions
+     * 
+     * @param writer the writer to close
+     */
+    public static void close(Writer writer)
+    {
+        try
+        {
+            if (writer != null)
+                writer.close();
+        } catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static byte[] readBytes(InputStream in)
+        throws IOException
+    {
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        copy(in,bout);
+        return bout.toByteArray();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * closes an output stream, and logs exceptions
+     *
+     * @param os the output stream to close
+     */
+    public static void close(OutputStream os)
+    {
+        try
+        {
+            if (os != null)
+                os.close();
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static OutputStream getNullStream()
+    {
+        return __nullStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An outputstream to nowhere
+     */
+    public static InputStream getClosedStream()
+    {
+        return __closedStream;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullOS extends OutputStream                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(byte[]b){}
+        @Override
+        public void write(byte[]b,int i,int l){}
+        @Override
+        public void write(int b){}
+    }
+    private static NullOS __nullStream = new NullOS();
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class ClosedIS extends InputStream                                    
+    {
+        @Override
+        public int read() throws IOException
+        {
+            return -1;
+        }
+    }
+    private static ClosedIS __closedStream = new ClosedIS();
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static Writer getNullWriter()
+    {
+        return __nullWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return An writer to nowhere
+     */
+    public static PrintWriter getNullPrintWriter()
+    {
+        return __nullPrintWriter;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class NullWrite extends Writer                                    
+    {
+        @Override
+        public void close(){}
+        @Override
+        public void flush(){}
+        @Override
+        public void write(char[]b){}
+        @Override
+        public void write(char[]b,int o,int l){}
+        @Override
+        public void write(int b){}
+        @Override
+        public void write(String s){}
+        @Override
+        public void write(String s,int o,int l){}
+    }
+    private static NullWrite __nullWriter = new NullWrite();
+    private static PrintWriter __nullPrintWriter = new PrintWriter(__nullWriter);
+}
+
+
+
+
+
+
+
+
+
diff --git a/src/java/org/eclipse/jetty/util/IPAddressMap.java b/src/java/org/eclipse/jetty/util/IPAddressMap.java
new file mode 100644
index 0000000..7cbbcab
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/IPAddressMap.java
@@ -0,0 +1,364 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Internet address map to object
+ * <p>
+ * Internet addresses may be specified as absolute address or as a combination of 
+ * four octet wildcard specifications (a.b.c.d) that are defined as follows.
+ * </p>
+ * <pre>
+ * nnn - an absolute value (0-255)
+ * mmm-nnn - an inclusive range of absolute values, 
+ *           with following shorthand notations:
+ *           nnn- => nnn-255
+ *           -nnn => 0-nnn
+ *           -    => 0-255
+ * a,b,... - a list of wildcard specifications
+ * </pre>
+ */
+@SuppressWarnings("serial")
+public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
+{
+    private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
+
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     */
+    public IPAddressMap()
+    {
+        super(11);
+    }
+   
+    /* --------------------------------------------------------------- */
+    /** Construct empty IPAddressMap.
+     * 
+     * @param capacity initial capacity
+     */
+    public IPAddressMap(int capacity)
+    {
+        super (capacity);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Insert a new internet address into map
+     * 
+     * @see java.util.HashMap#put(java.lang.Object, java.lang.Object)
+     */
+    @Override
+    public TYPE put(String addrSpec, TYPE object)
+        throws IllegalArgumentException
+    {
+        if (addrSpec == null || addrSpec.trim().length() == 0)
+            throw new IllegalArgumentException("Invalid IP address pattern: "+addrSpec);
+        
+        String spec = addrSpec.trim();
+        if (_patterns.get(spec) == null)
+            _patterns.put(spec,new IPAddrPattern(spec));
+        
+        return super.put(spec, object);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the object mapped to the specified internet address literal
+     * 
+     * @see java.util.HashMap#get(java.lang.Object)
+     */
+    @Override
+    public TYPE get(Object key)
+    {
+        return super.get(key);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first object that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return associated object
+     */
+    public TYPE match(String addr)
+    {
+        Map.Entry<String, TYPE> entry = getMatch(addr);
+        return entry==null ? null : entry.getValue();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve the first map entry that is associated with the specified 
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr internet address
+     * @return map entry associated
+     */
+    public Map.Entry<String, TYPE> getMatch(String addr)
+    {
+        if (addr != null)
+        {
+            for(Map.Entry<String, TYPE> entry: super.entrySet())
+            {
+                if (_patterns.get(entry.getKey()).match(addr))
+                {
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieve a lazy list of map entries associated with specified
+     * internet address by taking into account the wildcard specifications.
+     * 
+     * @param addr  internet address
+     * @return lazy list of map entries
+     */
+    public Object getLazyMatches(String addr)
+    {
+        if (addr == null)
+            return LazyList.getList(super.entrySet());
+        
+        Object entries = null;
+        for(Map.Entry<String, TYPE> entry: super.entrySet())
+        {
+            if (_patterns.get(entry.getKey()).match(addr))
+            {
+                entries = LazyList.add(entries,entry);
+            }
+        }
+        return entries;        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * IPAddrPattern
+     * 
+     * Represents internet address wildcard. 
+     * Matches the wildcard to provided internet address.
+     */
+    private static class IPAddrPattern
+    {
+        private final OctetPattern[] _octets = new OctetPattern[4];
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new IPAddrPattern
+         * 
+         * @param value internet address wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public IPAddrPattern(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                String part;
+                for (int idx=0; idx<4; idx++)
+                {
+                    part = parts.hasMoreTokens() ? parts.nextToken().trim() : "0-255";
+                    
+                    int len = part.length();
+                    if (len == 0 && parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address pattern: "+value);
+                    
+                    _octets[idx] = new OctetPattern(len==0 ? "0-255" : part);
+                }
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address pattern: "+value, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match the specified internet address against the wildcard
+         * 
+         * @param value internet address
+         * @return true if specified internet address matches wildcard specification
+         * 
+         * @throws IllegalArgumentException if specified internet address is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid IP address: "+value);
+            
+            try
+            {
+                StringTokenizer parts = new StringTokenizer(value, ".");
+                
+                boolean result = true;
+                for (int idx=0; idx<4; idx++)
+                {
+                    if (!parts.hasMoreTokens())
+                        throw new IllegalArgumentException("Invalid IP address: "+value);
+                        
+                    if (!(result &= _octets[idx].match(parts.nextToken())))
+                        break;
+                }
+                return result;
+            }
+            catch (IllegalArgumentException ex)
+            {
+                throw new IllegalArgumentException("Invalid IP address: "+value, ex);
+            }
+        }
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * OctetPattern
+     * 
+     * Represents a single octet wildcard.
+     * Matches the wildcard to the specified octet value.
+     */
+    private static class OctetPattern extends BitSet
+    {
+        private final BitSet _mask = new BitSet(256);
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Create new OctetPattern
+         * 
+         * @param octetSpec octet wildcard specification
+         * @throws IllegalArgumentException if wildcard specification is invalid
+         */
+        public OctetPattern(String octetSpec)
+            throws IllegalArgumentException
+        {
+            try
+            {
+                if (octetSpec != null)
+                {
+                    String spec = octetSpec.trim();
+                    if(spec.length() == 0)
+                    {
+                        _mask.set(0,255);
+                    }
+                    else
+                    {
+                        StringTokenizer parts = new StringTokenizer(spec,",");
+                        while (parts.hasMoreTokens())
+                        {
+                            String part = parts.nextToken().trim();
+                            if (part.length() > 0)
+                            {
+                                if (part.indexOf('-') < 0)
+                                {
+                                    Integer value = Integer.valueOf(part);
+                                    _mask.set(value);
+                                }
+                                else
+                                {
+                                    int low = 0, high = 255;
+                                    
+                                    String[] bounds = part.split("-",-2);
+                                    if (bounds.length != 2)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    if (bounds[0].length() > 0)
+                                    {
+                                        low = Integer.parseInt(bounds[0]);
+                                    }
+                                    if (bounds[1].length() > 0)
+                                    {
+                                        high = Integer.parseInt(bounds[1]);
+                                    }
+                                    
+                                    if (low > high)
+                                    {
+                                        throw new IllegalArgumentException("Invalid octet spec: "+octetSpec);
+                                    }
+                                    
+                                    _mask.set(low, high+1);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet spec: "+octetSpec, ex);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param value octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(String value)
+            throws IllegalArgumentException
+        {
+            if (value == null || value.trim().length() == 0)
+                throw new IllegalArgumentException("Invalid octet: "+value);
+
+            try
+            {
+                int number = Integer.parseInt(value);
+                return match(number);
+            }
+            catch (NumberFormatException ex)
+            {
+                throw new IllegalArgumentException("Invalid octet: "+value);
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        /**
+         * Match specified octet value against the wildcard
+         * 
+         * @param number octet value
+         * @return true if specified octet value matches the wildcard
+         * @throws IllegalArgumentException if specified octet value is invalid
+         */
+        public boolean match(int number)
+            throws IllegalArgumentException
+        {
+            if (number < 0 || number > 255)
+                throw new IllegalArgumentException("Invalid octet: "+number);
+            
+            return _mask.get(number);
+        }
+    }   
+}
diff --git a/src/java/org/eclipse/jetty/util/IntrospectionUtil.java b/src/java/org/eclipse/jetty/util/IntrospectionUtil.java
new file mode 100644
index 0000000..8588675
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/IntrospectionUtil.java
@@ -0,0 +1,300 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * IntrospectionUtil
+ *
+ *
+ */
+public class IntrospectionUtil
+{
+    
+    public static boolean isJavaBeanCompliantSetter (Method method)
+    {
+        if (method == null)
+            return false;
+        
+        if (method.getReturnType() != Void.TYPE)
+            return false;
+        
+        if (!method.getName().startsWith("set"))
+            return false;
+        
+        if (method.getParameterTypes().length != 1)
+            return false;
+        
+        return true;
+    }
+    
+    public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz == null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null || methodName.trim().equals(""))
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) && checkParams(methods[i].getParameterTypes(), (args==null?new Class[] {}:args), strictArgs))
+            {
+                method = methods[i];
+            }
+            
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else if (checkInheritance)
+                return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+        else
+            throw new NoSuchMethodException("No such method "+methodName+" on class "+clazz.getName());
+
+    }
+    
+    
+    
+    
+
+    public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz == null)
+            throw new NoSuchFieldException("No class");
+        if (targetName==null)
+            throw new NoSuchFieldException("No field name");
+        
+        try
+        {
+            Field field = clazz.getDeclaredField(targetName);
+            if (strictType)
+            {
+                if (field.getType().equals(targetType))
+                    return field;
+            }
+            else
+            {
+                if (field.getType().isAssignableFrom(targetType))
+                    return field;
+            }
+            if (checkInheritance)
+            {
+                    return findInheritedField(clazz.getPackage(), clazz.getSuperclass(), targetName, targetType, strictType);
+            }
+            else
+                throw new NoSuchFieldException("No field with name "+targetName+" in class "+clazz.getName()+" of type "+targetType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(),clazz.getSuperclass(), targetName,targetType,strictType);
+        }
+    }
+    
+    
+    
+    
+    
+    public static boolean isInheritable (Package pack, Member member)
+    {
+        if (pack==null)
+            return false;
+        if (member==null)
+            return false;
+        
+        int modifiers = member.getModifiers();
+        if (Modifier.isPublic(modifiers))
+            return true;
+        if (Modifier.isProtected(modifiers))
+            return true;
+        if (!Modifier.isPrivate(modifiers) && pack.equals(member.getDeclaringClass().getPackage()))
+            return true;
+       
+        return false;
+    }
+    
+   
+    
+    
+    public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
+    {
+        if (formalParams==null)
+            return actualParams==null;
+        if (actualParams==null)
+            return false;
+
+        if (formalParams.length!=actualParams.length)
+            return false;
+
+        if (formalParams.length==0)
+            return true; 
+        
+        int j=0;
+        if (strict)
+        {
+            while (j<formalParams.length && formalParams[j].equals(actualParams[j]))
+                j++;
+        }
+        else
+        { 
+            while ((j<formalParams.length) && (formalParams[j].isAssignableFrom(actualParams[j])))
+            {
+                j++;
+            }
+        }
+
+        if (j!=formalParams.length)
+        {
+            return false;
+        }
+
+        return true;
+    }
+    
+    
+    public static boolean isSameSignature (Method methodA, Method methodB)
+    {
+        if (methodA==null)
+            return false;
+        if (methodB==null)
+            return false;
+        
+        List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
+        List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
+       
+        if (methodA.getName().equals(methodB.getName())
+            &&
+            parameterTypesA.containsAll(parameterTypesB))
+            return true;
+        
+        return false;
+    }
+    
+    public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
+    {
+        if (formalType==null)
+            return actualType==null;
+        if (actualType==null)
+            return false;
+        
+        if (strict)
+            return formalType.equals(actualType);
+        else
+            return formalType.isAssignableFrom(actualType);
+    }
+
+    
+    
+    
+    public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(method.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean samesig = false;
+        Method[] methods = c.getDeclaredMethods();
+        for (int i=0; i<methods.length && !samesig; i++)
+        {
+            if (IntrospectionUtil.isSameSignature(method, methods[i]))
+                samesig = true;
+        }
+        return samesig;
+    }
+    
+    
+    public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
+    {
+        if (checkPackage)
+        {
+            if (!c.getPackage().equals(field.getDeclaringClass().getPackage()))
+                return false;
+        }
+        
+        boolean sameName = false;
+        Field[] fields = c.getDeclaredFields();
+        for (int i=0;i<fields.length && !sameName; i++)
+        {
+            if (fields[i].getName().equals(field.getName()))
+                sameName = true;
+        }
+        return sameName;
+    }
+    
+    
+    
+    protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
+    throws NoSuchMethodException
+    {
+        if (clazz==null)
+            throw new NoSuchMethodException("No class");
+        if (methodName==null)
+            throw new NoSuchMethodException("No method name");
+        
+        Method method = null;
+        Method[] methods = clazz.getDeclaredMethods();
+        for (int i=0;i<methods.length && method==null;i++)
+        {
+            if (methods[i].getName().equals(methodName) 
+                    && isInheritable(pack,methods[i])
+                    && checkParams(methods[i].getParameterTypes(), args, strictArgs))
+                method = methods[i];
+        }
+        if (method!=null)
+        {
+            return method;
+        }
+        else
+            return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
+    }
+    
+    protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
+    throws NoSuchFieldException
+    {
+        if (clazz==null)
+            throw new NoSuchFieldException ("No class");
+        if (fieldName==null)
+            throw new NoSuchFieldException ("No field name");
+        try
+        {
+            Field field = clazz.getDeclaredField(fieldName);
+            if (isInheritable(pack, field) && isTypeCompatible(fieldType, field.getType(), strictType))
+                return field;
+            else
+                return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType);
+        }
+        catch (NoSuchFieldException e)
+        {
+            return findInheritedField(clazz.getPackage(), clazz.getSuperclass(),fieldName, fieldType, strictType); 
+        }
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/util/LazyList.java b/src/java/org/eclipse/jetty/util/LazyList.java
new file mode 100644
index 0000000..b657fb6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/LazyList.java
@@ -0,0 +1,483 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/* ------------------------------------------------------------ */
+/** Lazy List creation.
+ * A List helper class that attempts to avoid unnecessary List
+ * creation.   If a method needs to create a List to return, but it is
+ * expected that this will either be empty or frequently contain a
+ * single item, then using LazyList will avoid additional object
+ * creations by using {@link Collections#EMPTY_LIST} or
+ * {@link Collections#singletonList(Object)} where possible.
+ * <p>
+ * LazyList works by passing an opaque representation of the list in
+ * and out of all the LazyList methods.  This opaque object is either
+ * null for an empty list, an Object for a list with a single entry
+ * or an {@link ArrayList} for a list of items.
+ *
+ * <p><h4>Usage</h4>
+ * <pre>
+ *   Object lazylist =null;
+ *   while(loopCondition)
+ *   {
+ *     Object item = getItem();
+ *     if (item.isToBeAdded())
+ *         lazylist = LazyList.add(lazylist,item);
+ *   }
+ *   return LazyList.getList(lazylist);
+ * </pre>
+ *
+ * An ArrayList of default size is used as the initial LazyList.
+ *
+ * @see java.util.List
+ */
+public class LazyList
+    implements Cloneable, Serializable
+{
+    private static final String[] __EMTPY_STRING_ARRAY = new String[0];
+    
+    /* ------------------------------------------------------------ */
+    private LazyList()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, Object item)
+    {
+        if (list==null)
+        {
+            if (item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(item);
+                return l;
+            }
+
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(item);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add an item to a LazyList 
+     * @param list The list to add to or null if none yet created.
+     * @param index The index to add the item at.
+     * @param item The item to add.
+     * @return The lazylist created or added to.
+     */
+    @SuppressWarnings("unchecked")
+    public static Object add(Object list, int index, Object item)
+    {
+        if (list==null)
+        {
+            if (index>0 || item instanceof List || item==null)
+            {
+                List<Object> l = new ArrayList<Object>();
+                l.add(index,item);
+                return l;
+            }
+            return item;
+        }
+
+        if (list instanceof List)
+        {
+            ((List<Object>)list).add(index,item);
+            return list;
+        }
+
+        List<Object> l=new ArrayList<Object>();
+        l.add(list);
+        l.add(index,item);
+        return l;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of a Collection to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param collection The Collection whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addCollection(Object list, Collection<?> collection)
+    {
+        Iterator<?> i=collection.iterator();
+        while(i.hasNext())
+            list=LazyList.add(list,i.next());
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add the contents of an array to a LazyList
+     * @param list The list to add to or null if none yet created.
+     * @param array The array whose contents should be added.
+     * @return The lazylist created or added to.
+     */
+    public static Object addArray(Object list, Object[] array)
+    {
+        for(int i=0;array!=null && i<array.length;i++)
+            list=LazyList.add(list,array[i]);
+        return list;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Ensure the capacity of the underlying list.
+     * 
+     */
+    public static Object ensureSize(Object list, int initialSize)
+    {
+        if (list==null)
+            return new ArrayList<Object>(initialSize);
+        if (list instanceof ArrayList)
+        {
+            ArrayList<?> ol=(ArrayList<?>)list;
+            if (ol.size()>initialSize)
+                return ol;
+            ArrayList<Object> nl = new ArrayList<Object>(initialSize);
+            nl.addAll(ol);
+            return nl;
+        }
+        List<Object> l= new ArrayList<Object>(initialSize);
+        l.add(list);
+        return l;    
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, Object o)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(o);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (list.equals(o))
+            return null;
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static Object remove(Object list, int i)
+    {
+        if (list==null)
+            return null;
+
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            l.remove(i);
+            if (l.size()==0)
+                return null;
+            return list;
+        }
+
+        if (i==0)
+            return null;
+        return list;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object)
+     * @return The List of added items, which may be an EMPTY_LIST
+     * or a SingletonList.
+     */
+    public static<E> List<E> getList(Object list)
+    {
+        return getList(list,false);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Get the real List from a LazyList.
+     * 
+     * @param list A LazyList returned from LazyList.add(Object) or null
+     * @param nullForEmpty If true, null is returned instead of an
+     * empty list.
+     * @return The List of added items, which may be null, an EMPTY_LIST
+     * or a SingletonList.
+     */
+    @SuppressWarnings("unchecked")
+    public static<E> List<E> getList(Object list, boolean nullForEmpty)
+    {
+        if (list==null)
+        {
+            if (nullForEmpty)
+                return null;
+            return Collections.emptyList();
+        }
+        if (list instanceof List)
+            return (List<E>)list;
+        
+        return (List<E>)Collections.singletonList(list);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    public static String[] toStringArray(Object list)
+    {
+        if (list==null)
+            return __EMTPY_STRING_ARRAY;
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            String[] a = new String[l.size()];
+            for (int i=l.size();i-->0;)
+            {
+                Object o=l.get(i);
+                if (o!=null)
+                    a[i]=o.toString();
+            }
+            return a;
+        }
+        
+        return new String[] {list.toString()};
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a lazylist to an array
+     * @param list The list to convert
+     * @param clazz The class of the array, which may be a primitive type
+     * @return array of the lazylist entries passed in
+     */
+    public static Object toArray(Object list,Class<?> clazz)
+    {
+        if (list==null)
+            return Array.newInstance(clazz,0);
+        
+        if (list instanceof List)
+        {
+            List<?> l = (List<?>)list;
+            if (clazz.isPrimitive())
+            {
+                Object a = Array.newInstance(clazz,l.size());
+                for (int i=0;i<l.size();i++)
+                    Array.set(a,i,l.get(i));
+                return a;
+            }
+            return l.toArray((Object[])Array.newInstance(clazz,l.size()));
+            
+        }
+        
+        Object a = Array.newInstance(clazz,1);
+        Array.set(a,0,list);
+        return a;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** The size of a lazy List 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @return the size of the list.
+     */
+    public static int size(Object list)
+    {
+        if (list==null)
+            return 0;
+        if (list instanceof List)
+            return ((List<?>)list).size();
+        return 1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get item from the list 
+     * @param list  A LazyList returned from LazyList.add(Object) or null
+     * @param i int index
+     * @return the item from the list.
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E get(Object list, int i)
+    {
+        if (list==null)
+            throw new IndexOutOfBoundsException();
+       
+        if (list instanceof List)
+            return (E)((List<?>)list).get(i);
+
+        if (i==0)
+            return (E)list;
+        
+        throw new IndexOutOfBoundsException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean contains(Object list,Object item)
+    {
+        if (list==null)
+            return false;
+        
+        if (list instanceof List)
+            return ((List<?>)list).contains(item);
+
+        return list.equals(item);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public static Object clone(Object list)
+    {
+        if (list==null)
+            return null;
+        if (list instanceof List)
+            return new ArrayList<Object>((List<?>)list);
+        return list;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String toString(Object list)
+    {
+        if (list==null)
+            return "[]";
+        if (list instanceof List)
+            return list.toString();
+        return "["+list+"]";
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> Iterator<E> iterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.iterator();
+        }
+        if (list instanceof List)
+        {
+            return ((List<E>)list).iterator();
+        }
+        List<E> l=getList(list);
+        return l.iterator();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("unchecked")
+    public static<E> ListIterator<E> listIterator(Object list)
+    {
+        if (list==null)
+        {
+            List<E> empty=Collections.emptyList();
+            return empty.listIterator();
+        }
+        if (list instanceof List)
+            return ((List<E>)list).listIterator();
+
+        List<E> l=getList(list);
+        return l.listIterator();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param array Any array of object
+     * @return A new <i>modifiable</i> list initialised with the elements from <code>array</code>.
+     */
+    public static<E> List<E> array2List(E[] array)
+    {	
+        if (array==null || array.length==0)
+            return new ArrayList<E>();
+        return new ArrayList<E>(Arrays.asList(array));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add element to an array
+     * @param array The array to add to (or null)
+     * @param item The item to add
+     * @param type The type of the array (in case of null array)
+     * @return new array with contents of array plus item
+     */
+    public static<T> T[] addToArray(T[] array, T item, Class<?> type)
+    {
+        if (array==null)
+        {
+            if (type==null && item!=null)
+                type= item.getClass();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(type, 1);
+            na[0]=item;
+            return na;
+        }
+        else
+        {
+            // TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+
+            Class<?> c = array.getClass().getComponentType();
+            @SuppressWarnings("unchecked")
+            T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
+            System.arraycopy(array, 0, na, 0, array.length);
+            na[array.length]=item;
+            return na;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static<T> T[] removeFromArray(T[] array, Object item)
+    {
+        if (item==null || array==null)
+            return array;
+        for (int i=array.length;i-->0;)
+        {
+            if (item.equals(array[i]))
+            {
+                Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
+                @SuppressWarnings("unchecked")
+                T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
+                if (i>0)
+                    System.arraycopy(array, 0, na, 0, i);
+                if (i+1<array.length)
+                    System.arraycopy(array, i+1, na, i, array.length-(i+1));
+                return na;
+            }
+        }
+        return array;
+    }
+    
+}
+
diff --git a/src/java/org/eclipse/jetty/util/Loader.java b/src/java/org/eclipse/jetty/util/Loader.java
new file mode 100644
index 0000000..fed2968
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Loader.java
@@ -0,0 +1,193 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------ */
+/** ClassLoader Helper.
+ * This helper class allows classes to be loaded either from the
+ * Thread's ContextClassLoader, the classloader of the derived class
+ * or the system ClassLoader.
+ *
+ * <B>Usage:</B><PRE>
+ * public class MyClass {
+ *     void myMethod() {
+ *          ...
+ *          Class c=Loader.loadClass(this.getClass(),classname);
+ *          ...
+ *     }
+ * </PRE>          
+ * 
+ */
+public class Loader
+{
+    /* ------------------------------------------------------------ */
+    public static URL getResource(Class<?> loadClass,String name, boolean checkParents)
+    {
+        URL url =null;
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        while (url==null && loader!=null )
+        {
+            url=loader.getResource(name); 
+            loader=(url==null&&checkParents)?loader.getParent():null;
+        }      
+        
+        loader=loadClass==null?null:loadClass.getClassLoader();
+        while (url==null && loader!=null )
+        {
+            url=loader.getResource(name); 
+            loader=(url==null&&checkParents)?loader.getParent():null;
+        }       
+
+        if (url==null)
+        {
+            url=ClassLoader.getSystemResource(name);
+        }   
+
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    @SuppressWarnings("rawtypes")
+    public static Class loadClass(Class loadClass,String name)
+        throws ClassNotFoundException
+    {
+        return loadClass(loadClass,name,false);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Load a class.
+     * 
+     * @param loadClass
+     * @param name
+     * @param checkParents If true, try loading directly from parent classloaders.
+     * @return Class
+     * @throws ClassNotFoundException
+     */
+    @SuppressWarnings("rawtypes")
+    public static Class loadClass(Class loadClass,String name,boolean checkParents)
+        throws ClassNotFoundException
+    {
+        ClassNotFoundException ex=null;
+        Class<?> c =null;
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        while (c==null && loader!=null )
+        {
+            try { c=loader.loadClass(name); }
+            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            loader=(c==null&&checkParents)?loader.getParent():null;
+        }      
+        
+        loader=loadClass==null?null:loadClass.getClassLoader();
+        while (c==null && loader!=null )
+        {
+            try { c=loader.loadClass(name); }
+            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+            loader=(c==null&&checkParents)?loader.getParent():null;
+        }       
+
+        if (c==null)
+        {
+            try { c=Class.forName(name); }
+            catch (ClassNotFoundException e) {if(ex==null)ex=e;}
+        }   
+
+        if (c!=null)
+            return c;
+        throw ex;
+    }
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
+        throws MissingResourceException
+    {
+        MissingResourceException ex=null;
+        ResourceBundle bundle =null;
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }      
+        
+        loader=loadClass==null?null:loadClass.getClassLoader();
+        while (bundle==null && loader!=null )
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale, loader); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+            loader=(bundle==null&&checkParents)?loader.getParent():null;
+        }       
+
+        if (bundle==null)
+        {
+            try { bundle=ResourceBundle.getBundle(name, locale); }
+            catch (MissingResourceException e) {if(ex==null)ex=e;}
+        }   
+
+        if (bundle!=null)
+            return bundle;
+        throw ex;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Generate the classpath (as a string) of all classloaders
+     * above the given classloader.
+     * 
+     * This is primarily used for jasper.
+     * @return the system class path
+     */
+    public static String getClassPath(ClassLoader loader) throws Exception
+    {
+        StringBuilder classpath=new StringBuilder();
+        while (loader != null && (loader instanceof URLClassLoader))
+        {
+            URL[] urls = ((URLClassLoader)loader).getURLs();
+            if (urls != null)
+            {     
+                for (int i=0;i<urls.length;i++)
+                {
+                    Resource resource = Resource.newResource(urls[i]);
+                    File file=resource.getFile();
+                    if (file!=null && file.exists())
+                    {
+                        if (classpath.length()>0)
+                            classpath.append(File.pathSeparatorChar);
+                        classpath.append(file.getAbsolutePath());
+                    }
+                }
+            }
+            loader = loader.getParent();
+        }
+        return classpath.toString();
+    }
+}
+
diff --git a/src/java/org/eclipse/jetty/util/MultiException.java b/src/java/org/eclipse/jetty/util/MultiException.java
new file mode 100644
index 0000000..eaac800
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/MultiException.java
@@ -0,0 +1,185 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.List;
+
+
+/* ------------------------------------------------------------ */
+/** Wraps multiple exceptions.
+ *
+ * Allows multiple exceptions to be thrown as a single exception.
+ *
+ * 
+ */
+@SuppressWarnings("serial")
+public class MultiException extends Exception
+{
+    private Object nested;
+
+    /* ------------------------------------------------------------ */
+    public MultiException()
+    {
+        super("Multiple exceptions");
+    }
+
+    /* ------------------------------------------------------------ */
+    public void add(Throwable e)
+    {
+        if (e instanceof MultiException)
+        {
+            MultiException me = (MultiException)e;
+            for (int i=0;i<LazyList.size(me.nested);i++)
+                nested=LazyList.add(nested,LazyList.get(me.nested,i));
+        }
+        else
+            nested=LazyList.add(nested,e);
+    }
+
+    /* ------------------------------------------------------------ */
+    public int size()
+    {
+        return LazyList.size(nested);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public List<Throwable> getThrowables()
+    {
+        return LazyList.getList(nested);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Throwable getThrowable(int i)
+    {
+        return (Throwable) LazyList.get(nested,i);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single exception that is thrown, otherwise the this
+     * multi exception is thrown. 
+     * @exception Exception 
+     */
+    public void ifExceptionThrow()
+        throws Exception
+    {
+        switch (LazyList.size(nested))
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=(Throwable)LazyList.get(nested,0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              if (th instanceof Exception)
+                  throw (Exception)th;
+          default:
+              throw this;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a Runtime exception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a single error or runtime exception that is thrown, otherwise the this
+     * multi exception is thrown, wrapped in a runtime exception. 
+     * @exception Error If this exception contains exactly 1 {@link Error} 
+     * @exception RuntimeException If this exception contains 1 {@link Throwable} but it is not an error,
+     *                             or it contains more than 1 {@link Throwable} of any type.
+     */
+    public void ifExceptionThrowRuntime()
+        throws Error
+    {
+        switch (LazyList.size(nested))
+        {
+          case 0:
+              break;
+          case 1:
+              Throwable th=(Throwable)LazyList.get(nested,0);
+              if (th instanceof Error)
+                  throw (Error)th;
+              else if (th instanceof RuntimeException)
+                  throw (RuntimeException)th;
+              else
+                  throw new RuntimeException(th);
+          default:
+              throw new RuntimeException(this);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Throw a multiexception.
+     * If this multi exception is empty then no action is taken. If it
+     * contains a any exceptions then this
+     * multi exception is thrown. 
+     */
+    public void ifExceptionThrowMulti()
+        throws MultiException
+    {
+        if (LazyList.size(nested)>0)
+            throw this;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        if (LazyList.size(nested)>0)
+            return MultiException.class.getSimpleName()+
+                LazyList.getList(nested);
+        return MultiException.class.getSimpleName()+"[]";
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void printStackTrace()
+    {
+        super.printStackTrace();
+        for (int i=0;i<LazyList.size(nested);i++)
+            ((Throwable)LazyList.get(nested,i)).printStackTrace();
+    }
+   
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
+     */
+    @Override
+    public void printStackTrace(PrintStream out)
+    {
+        super.printStackTrace(out);
+        for (int i=0;i<LazyList.size(nested);i++)
+            ((Throwable)LazyList.get(nested,i)).printStackTrace(out);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
+     */
+    @Override
+    public void printStackTrace(PrintWriter out)
+    {
+        super.printStackTrace(out);
+        for (int i=0;i<LazyList.size(nested);i++)
+            ((Throwable)LazyList.get(nested,i)).printStackTrace(out);
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/MultiMap.java b/src/java/org/eclipse/jetty/util/MultiMap.java
new file mode 100644
index 0000000..54e4e53
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/MultiMap.java
@@ -0,0 +1,415 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/* ------------------------------------------------------------ */
+/** A multi valued Map.
+ * This Map specializes HashMap and provides methods
+ * that operate on multi valued items. 
+ * <P>
+ * Implemented as a map of LazyList values
+ * @param <K> The key type of the map.
+ *
+ * @see LazyList
+ * 
+ */
+public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
+{
+    private static final long serialVersionUID = -6878723138353851005L;
+    Map<K,Object> _map;
+    ConcurrentMap<K, Object> _cmap;
+
+    public MultiMap()
+    {
+        _map=new HashMap<K, Object>();
+    }
+    
+    public MultiMap(Map<K,Object> map)
+    {
+        if (map instanceof ConcurrentMap)
+            _map=_cmap=new ConcurrentHashMap<K, Object>(map);
+        else
+            _map=new HashMap<K, Object>(map);
+    }
+    
+    public MultiMap(MultiMap<K> map)
+    {
+        if (map._cmap!=null)
+            _map=_cmap=new ConcurrentHashMap<K, Object>(map._cmap);
+        else
+            _map=new HashMap<K,Object>(map._map);
+    }
+    
+    public MultiMap(int capacity)
+    {
+        _map=new HashMap<K, Object>(capacity);
+    }
+    
+    public MultiMap(boolean concurrent)
+    {
+        if (concurrent)
+            _map=_cmap=new ConcurrentHashMap<K, Object>();
+        else
+            _map=new HashMap<K, Object>();
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /** Get multiple values.
+     * Single valued entries are converted to singleton lists.
+     * @param name The entry key. 
+     * @return Unmodifieable List of values.
+     */
+    public List getValues(Object name)
+    {
+        return LazyList.getList(_map.get(name),true);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get a value from a multiple value.
+     * If the value is not a multivalue, then index 0 retrieves the
+     * value or null.
+     * @param name The entry key.
+     * @param i Index of element to get.
+     * @return Unmodifieable List of values.
+     */
+    public Object getValue(Object name,int i)
+    {
+        Object l=_map.get(name);
+        if (i==0 && LazyList.size(l)==0)
+            return null;
+        return LazyList.get(l,i);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Get value as String.
+     * Single valued items are converted to a String with the toString()
+     * Object method. Multi valued entries are converted to a comma separated
+     * List.  No quoting of commas within values is performed.
+     * @param name The entry key. 
+     * @return String value.
+     */
+    public String getString(Object name)
+    {
+        Object l=_map.get(name);
+        switch(LazyList.size(l))
+        {
+          case 0:
+              return null;
+          case 1:
+              Object o=LazyList.get(l,0);
+              return o==null?null:o.toString();
+          default:
+          {
+              StringBuilder values=new StringBuilder(128);
+              for (int i=0; i<LazyList.size(l); i++)              
+              {
+                  Object e=LazyList.get(l,i);
+                  if (e!=null)
+                  {
+                      if (values.length()>0)
+                          values.append(',');
+                      values.append(e.toString());
+                  }
+              }   
+              return values.toString();
+          }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object get(Object name) 
+    {
+        Object l=_map.get(name);
+        switch(LazyList.size(l))
+        {
+          case 0:
+              return null;
+          case 1:
+              Object o=LazyList.get(l,0);
+              return o;
+          default:
+              return LazyList.getList(l,true);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put and entry into the map.
+     * @param name The entry key. 
+     * @param value The entry value.
+     * @return The previous value or null.
+     */
+    public Object put(K name, Object value) 
+    {
+        return _map.put(name,LazyList.add(null,value));
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     * @return The previous value or null.
+     */
+    public Object putValues(K name, List<? extends Object> values) 
+    {
+        return _map.put(name,values);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Put multi valued entry.
+     * @param name The entry key. 
+     * @param values The String array of multiple values.
+     * @return The previous value or null.
+     */
+    public Object putValues(K name, String... values) 
+    {
+        Object list=null;
+        for (int i=0;i<values.length;i++)
+            list=LazyList.add(list,values[i]);
+        return _map.put(name,list);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Add value to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param value The entry value.
+     */
+    public void add(K name, Object value) 
+    {
+        Object lo = _map.get(name);
+        Object ln = LazyList.add(lo,value);
+        if (lo!=ln)
+            _map.put(name,ln);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The List of multiple values.
+     */
+    public void addValues(K name, List<? extends Object> values) 
+    {
+        Object lo = _map.get(name);
+        Object ln = LazyList.addCollection(lo,values);
+        if (lo!=ln)
+            _map.put(name,ln);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add values to multi valued entry.
+     * If the entry is single valued, it is converted to the first
+     * value of a multi valued entry.
+     * @param name The entry key. 
+     * @param values The String array of multiple values.
+     */
+    public void addValues(K name, String[] values) 
+    {
+        Object lo = _map.get(name);
+        Object ln = LazyList.addCollection(lo,Arrays.asList(values));
+        if (lo!=ln)
+            _map.put(name,ln);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Remove value.
+     * @param name The entry key. 
+     * @param value The entry value. 
+     * @return true if it was removed.
+     */
+    public boolean removeValue(K name,Object value)
+    {
+        Object lo = _map.get(name);
+        Object ln=lo;
+        int s=LazyList.size(lo);
+        if (s>0)
+        {
+            ln=LazyList.remove(lo,value);
+            if (ln==null)
+                _map.remove(name);
+            else
+                _map.put(name, ln);
+        }
+        return LazyList.size(ln)!=s;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Put all contents of map.
+     * @param m Map
+     */
+    public void putAll(Map<? extends K, ? extends Object> m)
+    {
+        boolean multi = (m instanceof MultiMap);
+
+        if (multi)
+        {
+            for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
+            {
+                _map.put(entry.getKey(),LazyList.clone(entry.getValue()));
+            }
+        }
+        else
+        {
+            _map.putAll(m);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Map of String arrays
+     */
+    public Map<K,String[]> toStringArrayMap()
+    {
+        HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2)
+        {
+            public String toString()
+            {
+                StringBuilder b=new StringBuilder();
+                b.append('{');
+                for (K k:keySet())
+                {
+                    if(b.length()>1)
+                        b.append(',');
+                    b.append(k);
+                    b.append('=');
+                    b.append(Arrays.asList(get(k)));
+                }
+
+                b.append('}');
+                return b.toString();
+            }
+        };
+        
+        for(Map.Entry<K,Object> entry: _map.entrySet())
+        {
+            String[] a = LazyList.toStringArray(entry.getValue());
+            map.put(entry.getKey(),a);
+        }
+        return map;
+    }
+
+    @Override
+    public String toString()
+    {
+        return _cmap==null?_map.toString():_cmap.toString();
+    }
+    
+    public void clear()
+    {
+        _map.clear();
+    }
+
+    public boolean containsKey(Object key)
+    {
+        return _map.containsKey(key);
+    }
+
+    public boolean containsValue(Object value)
+    {
+        return _map.containsValue(value);
+    }
+
+    public Set<Entry<K, Object>> entrySet()
+    {
+        return _map.entrySet();
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return _map.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return _map.hashCode();
+    }
+
+    public boolean isEmpty()
+    {
+        return _map.isEmpty();
+    }
+
+    public Set<K> keySet()
+    {
+        return _map.keySet();
+    }
+
+    public Object remove(Object key)
+    {
+        return _map.remove(key);
+    }
+
+    public int size()
+    {
+        return _map.size();
+    }
+
+    public Collection<Object> values()
+    {
+        return _map.values();
+    }
+
+    
+    
+    public Object putIfAbsent(K key, Object value)
+    {
+        if (_cmap==null)
+            throw new UnsupportedOperationException();
+        return _cmap.putIfAbsent(key,value);
+    }
+
+    public boolean remove(Object key, Object value)
+    {
+        if (_cmap==null)
+            throw new UnsupportedOperationException();
+        return _cmap.remove(key,value);
+    }
+
+    public boolean replace(K key, Object oldValue, Object newValue)
+    {
+        if (_cmap==null)
+            throw new UnsupportedOperationException();
+        return _cmap.replace(key,oldValue,newValue);
+    }
+
+    public Object replace(K key, Object value)
+    {
+        if (_cmap==null)
+            throw new UnsupportedOperationException();
+        return _cmap.replace(key,value);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/MultiPartInputStream.java b/src/java/org/eclipse/jetty/util/MultiPartInputStream.java
new file mode 100644
index 0000000..6aae849
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/MultiPartInputStream.java
@@ -0,0 +1,851 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.http.Part;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/**
+ * MultiPartInputStream
+ *
+ * Handle a MultiPart Mime input stream, breaking it up on the boundary into files and strings.
+ */
+public class MultiPartInputStream
+{
+    private static final Logger LOG = Log.getLogger(MultiPartInputStream.class);
+
+    public static final MultipartConfigElement  __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir"));
+    protected InputStream _in;
+    protected MultipartConfigElement _config;
+    protected String _contentType;
+    protected MultiMap<String> _parts;
+    protected File _tmpDir;
+    protected File _contextTmpDir;
+    protected boolean _deleteOnExit;
+    
+    
+    
+    public class MultiPart implements Part
+    {
+        protected String _name;
+        protected String _filename;
+        protected File _file;
+        protected OutputStream _out;
+        protected ByteArrayOutputStream2 _bout;
+        protected String _contentType;
+        protected MultiMap<String> _headers;
+        protected long _size = 0;
+        protected boolean _temporary = true;
+
+        public MultiPart (String name, String filename) 
+        throws IOException
+        {
+            _name = name;
+            _filename = filename;
+        }
+
+        protected void setContentType (String contentType)
+        {
+            _contentType = contentType;
+        }
+        
+        
+        protected void open() 
+        throws IOException
+        {
+            //We will either be writing to a file, if it has a filename on the content-disposition
+            //and otherwise a byte-array-input-stream, OR if we exceed the getFileSizeThreshold, we
+            //will need to change to write to a file.           
+            if (_filename != null && _filename.trim().length() > 0)
+            {
+                createFile();            
+            }
+            else
+            {
+                //Write to a buffer in memory until we discover we've exceed the 
+                //MultipartConfig fileSizeThreshold
+                _out = _bout= new ByteArrayOutputStream2();
+            }
+        }
+        
+        protected void close() 
+        throws IOException
+        {
+            _out.close();
+        }
+        
+      
+        protected void write (int b)
+        throws IOException
+        {      
+            if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + 1 > MultiPartInputStream.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+            
+            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + 1 > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+            _out.write(b);   
+            _size ++;
+        }
+        
+        protected void write (byte[] bytes, int offset, int length) 
+        throws IOException
+        { 
+            if (MultiPartInputStream.this._config.getMaxFileSize() > 0 && _size + length > MultiPartInputStream.this._config.getMaxFileSize())
+                throw new IllegalStateException ("Multipart Mime part "+_name+" exceeds max filesize");
+            
+            if (MultiPartInputStream.this._config.getFileSizeThreshold() > 0 && _size + length > MultiPartInputStream.this._config.getFileSizeThreshold() && _file==null)
+                createFile();
+            
+            _out.write(bytes, offset, length);
+            _size += length;
+        }
+        
+        protected void createFile ()
+        throws IOException
+        {
+            _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
+            if (_deleteOnExit)
+                _file.deleteOnExit();
+            FileOutputStream fos = new FileOutputStream(_file);
+            BufferedOutputStream bos = new BufferedOutputStream(fos);
+            
+            if (_size > 0 && _out != null)
+            {
+                //already written some bytes, so need to copy them into the file
+                _out.flush();
+                _bout.writeTo(bos);
+                _out.close();
+                _bout = null;
+            }
+            _out = bos;
+        }
+        
+
+        
+        protected void setHeaders(MultiMap<String> headers)
+        {
+            _headers = headers;
+        }
+        
+        /** 
+         * @see javax.servlet.http.Part#getContentType()
+         */
+        public String getContentType()
+        {
+            return _contentType;
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#getHeader(java.lang.String)
+         */
+        public String getHeader(String name)
+        {
+            if (name == null)
+                return null;
+            return (String)_headers.getValue(name.toLowerCase(Locale.ENGLISH), 0);
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#getHeaderNames()
+         */
+        public Collection<String> getHeaderNames()
+        {
+            return _headers.keySet();
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#getHeaders(java.lang.String)
+         */
+        public Collection<String> getHeaders(String name)
+        {
+           return _headers.getValues(name);
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#getInputStream()
+         */
+        public InputStream getInputStream() throws IOException
+        {
+           if (_file != null)
+           {
+               //written to a file, whether temporary or not
+               return new BufferedInputStream (new FileInputStream(_file));
+           }
+           else
+           {
+               //part content is in memory
+               return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
+           }
+        }
+
+        public byte[] getBytes()
+        {
+            if (_bout!=null)
+                return _bout.toByteArray();
+            return null;
+        }
+        
+        /** 
+         * @see javax.servlet.http.Part#getName()
+         */
+        public String getName()
+        {
+           return _name;
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#getSize()
+         */
+        public long getSize()
+        {
+            return _size;         
+        }
+
+        /** 
+         * @see javax.servlet.http.Part#write(java.lang.String)
+         */
+        public void write(String fileName) throws IOException
+        {
+            if (_file == null)
+            {
+                _temporary = false;
+                
+                //part data is only in the ByteArrayOutputStream and never been written to disk
+                _file = new File (_tmpDir, fileName);
+
+                BufferedOutputStream bos = null;
+                try
+                {
+                    bos = new BufferedOutputStream(new FileOutputStream(_file));
+                    _bout.writeTo(bos);
+                    bos.flush();
+                }
+                finally
+                {
+                    if (bos != null)
+                        bos.close();
+                    _bout = null;
+                }
+            }
+            else
+            {
+                //the part data is already written to a temporary file, just rename it
+                _temporary = false;
+                
+                File f = new File(_tmpDir, fileName);
+                if (_file.renameTo(f))
+                    _file = f;
+            }
+        }
+        
+        /** 
+         * Remove the file, whether or not Part.write() was called on it
+         * (ie no longer temporary)
+         * @see javax.servlet.http.Part#delete()
+         */
+        public void delete() throws IOException
+        {
+            if (_file != null && _file.exists())
+                _file.delete();     
+        }
+        
+        /**
+         * Only remove tmp files.
+         * 
+         * @throws IOException
+         */
+        public void cleanUp() throws IOException
+        {
+            if (_temporary && _file != null && _file.exists())
+                _file.delete();
+        }
+        
+        
+        /**
+         * Get the file, if any, the data has been written to.
+         * @return
+         */
+        public File getFile ()
+        {
+            return _file;
+        }  
+        
+        
+        /**
+         * Get the filename from the content-disposition.
+         * @return null or the filename
+         */
+        public String getContentDispositionFilename ()
+        {
+            return _filename;
+        }
+    }
+    
+    
+    
+    
+    /**
+     * @param in Request input stream 
+     * @param contentType Content-Type header
+     * @param config MultipartConfigElement 
+     * @param contextTmpDir javax.servlet.context.tempdir
+     */
+    public MultiPartInputStream (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir)
+    {
+        _in = new ReadLineInputStream(in);
+       _contentType = contentType;
+       _config = config;
+       _contextTmpDir = contextTmpDir;
+       if (_contextTmpDir == null)
+           _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
+       
+       if (_config == null)
+           _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
+    }
+
+    /**
+     * Get the already parsed parts.
+     * 
+     * @return
+     */
+    public Collection<Part> getParsedParts()
+    {
+        if (_parts == null)
+            return Collections.emptyList();
+
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+    
+    /**
+     * Delete any tmp storage for parts, and clear out the parts list.
+     * 
+     * @throws MultiException
+     */
+    public void deleteParts ()
+    throws MultiException
+    {
+        Collection<Part> parts = getParsedParts();
+        MultiException err = new MultiException();
+        for (Part p:parts)
+        {
+            try
+            {
+                ((MultiPartInputStream.MultiPart)p).cleanUp();
+            } 
+            catch(Exception e)
+            {     
+                err.add(e); 
+            }
+        }
+        _parts.clear();
+        
+        err.ifExceptionThrowMulti();
+    }
+
+   
+    /**
+     * Parse, if necessary, the multipart data and return the list of Parts.
+     * 
+     * @return
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Collection<Part> getParts()
+    throws IOException, ServletException
+    {
+        parse();
+        Collection<Object> values = _parts.values();
+        List<Part> parts = new ArrayList<Part>();
+        for (Object o: values)
+        {
+            List<Part> asList = LazyList.getList(o, false);
+            parts.addAll(asList);
+        }
+        return parts;
+    }
+    
+    
+    /**
+     * Get the named Part.
+     * 
+     * @param name
+     * @return
+     * @throws IOException
+     * @throws ServletException
+     */
+    public Part getPart(String name)
+    throws IOException, ServletException
+    {
+        parse();
+        return (Part)_parts.getValue(name, 0);
+    }
+    
+    
+    /**
+     * Parse, if necessary, the multipart stream.
+     * 
+     * @throws IOException
+     * @throws ServletException
+     */
+    protected void parse ()
+    throws IOException, ServletException
+    {
+        //have we already parsed the input?
+        if (_parts != null)
+            return;
+        
+        //initialize
+        long total = 0; //keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize              
+        _parts = new MultiMap<String>();
+
+        //if its not a multipart request, don't parse it
+        if (_contentType == null || !_contentType.startsWith("multipart/form-data"))
+            return;
+ 
+        //sort out the location to which to write the files
+        
+        if (_config.getLocation() == null)
+            _tmpDir = _contextTmpDir;
+        else if ("".equals(_config.getLocation()))
+            _tmpDir = _contextTmpDir;
+        else
+        {
+            File f = new File (_config.getLocation());
+            if (f.isAbsolute())
+                _tmpDir = f;
+            else
+                _tmpDir = new File (_contextTmpDir, _config.getLocation());
+        }
+      
+        if (!_tmpDir.exists())
+            _tmpDir.mkdirs();
+
+        String contentTypeBoundary = "";
+        int bstart = _contentType.indexOf("boundary=");
+        if (bstart >= 0)
+        {
+            int bend = _contentType.indexOf(";", bstart);
+            bend = (bend < 0? _contentType.length(): bend);
+            contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart,bend), true).trim());
+        }
+        
+        String boundary="--"+contentTypeBoundary;
+        byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1);
+
+        // Get first boundary
+        String line = null;
+        try
+        {
+            line=((ReadLineInputStream)_in).readLine();  
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Badly formatted multipart request");
+            throw e;
+        }
+
+        if (line == null)
+            throw new IOException("Missing content for multipart request");
+
+        boolean badFormatLogged = false;
+        line=line.trim();
+        while (line != null && !line.equals(boundary))
+        {
+            if (!badFormatLogged)
+            {
+                LOG.warn("Badly formatted multipart request");
+                badFormatLogged = true;
+            }
+            line=((ReadLineInputStream)_in).readLine();
+            line=(line==null?line:line.trim());
+        }
+
+        if (line == null)
+            throw new IOException("Missing initial multi part boundary");
+
+        // Read each part
+        boolean lastPart=false;
+
+        outer:while(!lastPart)
+        {
+            String contentDisposition=null;
+            String contentType=null;
+            String contentTransferEncoding=null;
+            
+            MultiMap<String> headers = new MultiMap<String>();
+            while(true)
+            {
+                line=((ReadLineInputStream)_in).readLine();
+                
+                //No more input
+                if(line==null)
+                    break outer;
+
+                // If blank line, end of part headers
+                if("".equals(line))
+                    break;
+                
+                total += line.length();
+                if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                    throw new IllegalStateException ("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+
+                //get content-disposition and content-type
+                int c=line.indexOf(':',0);
+                if(c>0)
+                {
+                    String key=line.substring(0,c).trim().toLowerCase(Locale.ENGLISH);
+                    String value=line.substring(c+1,line.length()).trim();
+                    headers.put(key, value);
+                    if (key.equalsIgnoreCase("content-disposition"))
+                        contentDisposition=value;
+                    if (key.equalsIgnoreCase("content-type"))
+                        contentType = value;
+                    if(key.equals("content-transfer-encoding"))
+                        contentTransferEncoding=value;
+
+                }
+            }
+
+            // Extract content-disposition
+            boolean form_data=false;
+            if(contentDisposition==null)
+            {
+                throw new IOException("Missing content-disposition");
+            }
+
+            QuotedStringTokenizer tok=new QuotedStringTokenizer(contentDisposition,";", false, true);
+            String name=null;
+            String filename=null;
+            while(tok.hasMoreTokens())
+            {
+                String t=tok.nextToken().trim();
+                String tl=t.toLowerCase(Locale.ENGLISH);
+                if(t.startsWith("form-data"))
+                    form_data=true;
+                else if(tl.startsWith("name="))
+                    name=value(t, true);
+                else if(tl.startsWith("filename="))
+                    filename=filenameValue(t);
+            }
+
+            // Check disposition
+            if(!form_data)
+            {
+                continue;
+            }
+            //It is valid for reset and submit buttons to have an empty name.
+            //If no name is supplied, the browser skips sending the info for that field.
+            //However, if you supply the empty string as the name, the browser sends the
+            //field, with name as the empty string. So, only continue this loop if we
+            //have not yet seen a name field.
+            if(name==null)
+            {
+                continue;
+            }
+
+            //Have a new Part
+            MultiPart part = new MultiPart(name, filename);
+            part.setHeaders(headers);
+            part.setContentType(contentType);
+            _parts.add(name, part);
+            part.open();
+            
+            InputStream partInput = null;
+            if ("base64".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new Base64InputStream((ReadLineInputStream)_in);
+            }
+            else if ("quoted-printable".equalsIgnoreCase(contentTransferEncoding))
+            {
+                partInput = new FilterInputStream(_in)
+                {
+                    @Override
+                    public int read() throws IOException
+                    {
+                        int c = in.read();
+                        if (c >= 0 && c == '=')
+                        {
+                            int hi = in.read();
+                            int lo = in.read();
+                            if (hi < 0 || lo < 0)
+                            {
+                                throw new IOException("Unexpected end to quoted-printable byte");
+                            }
+                            char[] chars = new char[] { (char)hi, (char)lo };
+                            c = Integer.parseInt(new String(chars),16);
+                        }
+                        return c;
+                    }
+                };
+            }
+            else
+                partInput = _in;
+            
+            try
+            { 
+                int state=-2;
+                int c;
+                boolean cr=false;
+                boolean lf=false;
+
+                // loop for all lines
+                while(true)
+                {
+                    int b=0;
+                    while((c=(state!=-2)?state:partInput.read())!=-1)
+                    {
+                        total ++;
+                        if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize())
+                            throw new IllegalStateException("Request exceeds maxRequestSize ("+_config.getMaxRequestSize()+")");
+                        
+                        state=-2;
+                        
+                        // look for CR and/or LF
+                        if(c==13||c==10)
+                        {
+                            if(c==13)
+                            {
+                                partInput.mark(1);
+                                int tmp=partInput.read();
+                                if (tmp!=10)
+                                    partInput.reset();
+                                else
+                                    state=tmp;
+                            }
+                            break;
+                        }
+                        
+                        // Look for boundary
+                        if(b>=0&&b<byteBoundary.length&&c==byteBoundary[b])
+                        {
+                            b++;
+                        }
+                        else
+                        {
+                            // Got a character not part of the boundary, so we don't have the boundary marker.
+                            // Write out as many chars as we matched, then the char we're looking at.
+                            if(cr)
+                                part.write(13);
+                    
+                            if(lf)
+                                part.write(10); 
+                            
+                            cr=lf=false;
+                            if(b>0)
+                                part.write(byteBoundary,0,b);
+                              
+                            b=-1;
+                            part.write(c);
+                        }
+                    }
+                    
+                    // Check for incomplete boundary match, writing out the chars we matched along the way
+                    if((b>0&&b<byteBoundary.length-2)||(b==byteBoundary.length-1))
+                    {
+                        if(cr)
+                            part.write(13);
+
+                        if(lf)
+                            part.write(10);
+
+                        cr=lf=false;
+                        part.write(byteBoundary,0,b);
+                        b=-1;
+                    }
+                    
+                    // Boundary match. If we've run out of input or we matched the entire final boundary marker, then this is the last part.
+                    if(b>0||c==-1)
+                    {
+                       
+                        if(b==byteBoundary.length)
+                            lastPart=true;
+                        if(state==10)
+                            state=-2;
+                        break;
+                    }
+                    
+                    // handle CR LF
+                    if(cr)
+                        part.write(13); 
+
+                    if(lf)
+                        part.write(10);
+
+                    cr=(c==13);
+                    lf=(c==10||state==10);
+                    if(state==10)
+                        state=-2;
+                }
+            }
+            finally
+            {
+                part.close();
+            }
+        }
+        if (!lastPart)
+            throw new IOException("Incomplete parts");
+    }
+    
+    public void setDeleteOnExit(boolean deleteOnExit)
+    {
+        _deleteOnExit = deleteOnExit;
+    }
+
+
+    public boolean isDeleteOnExit()
+    {
+        return _deleteOnExit;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private String value(String nameEqualsValue, boolean splitAfterSpace)
+    {
+        /*
+        String value=nameEqualsValue.substring(nameEqualsValue.indexOf('=')+1).trim();
+        int i=value.indexOf(';');
+        if(i>0)
+            value=value.substring(0,i);
+        if(value.startsWith("\""))
+        {
+            value=value.substring(1,value.indexOf('"',1));
+        }
+        else if (splitAfterSpace)
+        {
+            i=value.indexOf(' ');
+            if(i>0)
+                value=value.substring(0,i);
+        }
+        return value;
+        */
+         int idx = nameEqualsValue.indexOf('=');
+         String value = nameEqualsValue.substring(idx+1).trim();
+         return QuotedStringTokenizer.unquoteOnly(value);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private String filenameValue(String nameEqualsValue)
+    {
+        int idx = nameEqualsValue.indexOf('=');
+        String value = nameEqualsValue.substring(idx+1).trim();   
+
+        if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*"))
+        {
+            //incorrectly escaped IE filenames that have the whole path
+            //we just strip any leading & trailing quotes and leave it as is
+            char first=value.charAt(0);
+            if (first=='"' || first=='\'')
+                value=value.substring(1);
+            char last=value.charAt(value.length()-1);
+            if (last=='"' || last=='\'')
+                value = value.substring(0,value.length()-1);
+
+            return value;
+        }
+        else
+            //unquote the string, but allow any backslashes that don't
+            //form a valid escape sequence to remain as many browsers
+            //even on *nix systems will not escape a filename containing
+            //backslashes
+            return QuotedStringTokenizer.unquoteOnly(value, true);
+    }
+    
+    private static class Base64InputStream extends InputStream
+    {
+        ReadLineInputStream _in;
+        String _line;
+        byte[] _buffer;
+        int _pos;
+
+    
+        public Base64InputStream(ReadLineInputStream rlis)
+        {
+            _in = rlis;
+        }
+
+        @Override
+        public int read() throws IOException
+        {
+            if (_buffer==null || _pos>= _buffer.length)
+            {
+                //Any CR and LF will be consumed by the readLine() call.
+                //We need to put them back into the bytes returned from this
+                //method because the parsing of the multipart content uses them
+                //as markers to determine when we've reached the end of a part.
+                _line = _in.readLine(); 
+                if (_line==null)
+                    return -1;  //nothing left
+                if (_line.startsWith("--"))
+                    _buffer=(_line+"\r\n").getBytes(); //boundary marking end of part
+                else if (_line.length()==0)
+                    _buffer="\r\n".getBytes(); //blank line
+                else
+                {
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream((4*_line.length()/3)+2);
+                    B64Code.decode(_line, baos);
+                    baos.write(13);
+                    baos.write(10);
+                    _buffer = baos.toByteArray();
+                }
+
+                _pos=0;
+            }
+            
+            return _buffer[_pos++];
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/MultiPartOutputStream.java b/src/java/org/eclipse/jetty/util/MultiPartOutputStream.java
new file mode 100644
index 0000000..195e01d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/MultiPartOutputStream.java
@@ -0,0 +1,140 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartOutputStream extends FilterOutputStream
+{
+    /* ------------------------------------------------------------ */
+    private static final byte[] __CRLF={'\r','\n'};
+    private static final byte[] __DASHDASH={'-','-'};
+    
+    public static String MULTIPART_MIXED="multipart/mixed";
+    public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace";
+    
+    /* ------------------------------------------------------------ */
+    private String boundary;
+    private byte[] boundaryBytes;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartOutputStream(OutputStream out)
+    throws IOException
+    {
+        super(out);
+
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1);
+
+        inPart=false;
+    }
+
+    
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__DASHDASH);
+        out.write(__CRLF);
+        inPart=false;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+
+    public OutputStream getOut() {return out;}
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1));
+        out.write(__CRLF);
+        out.write(__CRLF);
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=true;
+        out.write(__DASHDASH);
+        out.write(boundaryBytes);
+        out.write(__CRLF);
+        if (contentType != null)
+            out.write(("Content-Type: "+contentType).getBytes(StringUtil.__ISO_8859_1));
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i].getBytes(StringUtil.__ISO_8859_1));
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException
+    {
+        out.write(b,off,len);
+    }
+}
+
+
+
+
diff --git a/src/java/org/eclipse/jetty/util/MultiPartWriter.java b/src/java/org/eclipse/jetty/util/MultiPartWriter.java
new file mode 100644
index 0000000..753db07
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/MultiPartWriter.java
@@ -0,0 +1,138 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+
+/* ================================================================ */
+/** Handle a multipart MIME response.
+ *
+ * 
+ * 
+*/
+public class MultiPartWriter extends FilterWriter
+{
+    /* ------------------------------------------------------------ */
+    private final static String __CRLF="\015\012";
+    private final static String __DASHDASH="--";
+    
+    public static String MULTIPART_MIXED=MultiPartOutputStream.MULTIPART_MIXED;
+    public static String MULTIPART_X_MIXED_REPLACE=MultiPartOutputStream.MULTIPART_X_MIXED_REPLACE;
+    
+    /* ------------------------------------------------------------ */
+    private String boundary;
+
+    /* ------------------------------------------------------------ */
+    private boolean inPart=false;    
+    
+    /* ------------------------------------------------------------ */
+    public MultiPartWriter(Writer out)
+         throws IOException
+    {
+        super(out);
+        boundary = "jetty"+System.identityHashCode(this)+
+        Long.toString(System.currentTimeMillis(),36);
+        
+        inPart=false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** End the current part.
+     * @exception IOException IOException
+     */
+    @Override
+    public void close()
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__DASHDASH);
+        out.write(__CRLF);
+        inPart=false;
+        super.close();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getBoundary()
+    {
+        return boundary;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** end creation of the next Content.
+     */
+    public void endPart()
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        inPart=false;
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Start creation of the next Content.
+     */
+    public void startPart(String contentType, String[] headers)
+         throws IOException
+    {
+        if (inPart)
+            out.write(__CRLF);
+        out.write(__DASHDASH);
+        out.write(boundary);
+        out.write(__CRLF);
+        out.write("Content-Type: ");
+        out.write(contentType);
+        out.write(__CRLF);
+        for (int i=0;headers!=null && i<headers.length;i++)
+        {
+            out.write(headers[i]);
+            out.write(__CRLF);
+        }
+        out.write(__CRLF);
+        inPart=true;
+    }
+    
+}
+
+
+
+
diff --git a/src/java/org/eclipse/jetty/util/PatternMatcher.java b/src/java/org/eclipse/jetty/util/PatternMatcher.java
new file mode 100644
index 0000000..0afda65
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/PatternMatcher.java
@@ -0,0 +1,104 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public abstract class PatternMatcher
+{
+    public abstract void matched (URI uri) throws Exception;
+    
+    
+    /**
+     * Find jar names from the provided list matching a pattern.
+     * 
+     * If the pattern is null and isNullInclusive is true, then
+     * all jar names will match.
+     * 
+     * A pattern is a set of acceptable jar names. Each acceptable
+     * jar name is a regex. Each regex can be separated by either a
+     * "," or a "|". If you use a "|" this or's together the jar
+     * name patterns. This means that ordering of the matches is
+     * unimportant to you. If instead, you want to match particular
+     * jar names, and you want to match them in order, you should
+     * separate the regexs with "," instead. 
+     * 
+     * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+     * Will iterate over the jar names and match
+     * in any order.
+     * 
+     * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+     * Will iterate over the jar names, matching
+     * all those starting with "aaa-" first, then "bbb-".
+     *
+     * @param pattern the pattern
+     * @param uris the uris to test the pattern against
+     * @param isNullInclusive if true, an empty pattern means all names match, if false, none match
+     * @throws Exception
+     */
+    public void match (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        if (uris!=null)
+        {
+            String[] patterns = (pattern==null?null:pattern.pattern().split(","));
+
+            List<Pattern> subPatterns = new ArrayList<Pattern>();
+            for (int i=0; patterns!=null && i<patterns.length;i++)
+            {
+                subPatterns.add(Pattern.compile(patterns[i]));
+            }
+            if (subPatterns.isEmpty())
+                subPatterns.add(pattern);
+
+            if (subPatterns.isEmpty())
+            {
+                matchPatterns(null, uris, isNullInclusive);
+            }
+            else
+            {
+                //for each subpattern, iterate over all the urls, processing those that match
+                for (Pattern p : subPatterns)
+                {
+                    matchPatterns(p, uris, isNullInclusive);
+                }
+            }
+        }
+    }
+
+
+    public void matchPatterns (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+        for (int i=0; i<uris.length;i++)
+        {
+            URI uri = uris[i];
+            String s = uri.toString();
+            if ((pattern == null && isNullInclusive)
+                    ||
+                    (pattern!=null && pattern.matcher(s).matches()))
+            {
+                matched(uris[i]);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/src/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
new file mode 100644
index 0000000..dc85da1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/QuotedStringTokenizer.java
@@ -0,0 +1,603 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+
+/* ------------------------------------------------------------ */
+/** StringTokenizer with Quoting support.
+ *
+ * This class is a copy of the java.util.StringTokenizer API and
+ * the behaviour is the same, except that single and double quoted
+ * string values are recognised.
+ * Delimiters within quotes are not considered delimiters.
+ * Quotes can be escaped with '\'.
+ *
+ * @see java.util.StringTokenizer
+ *
+ */
+public class QuotedStringTokenizer
+    extends StringTokenizer
+{
+    private final static String __delim="\t\n\r";
+    private String _string;
+    private String _delim = __delim;
+    private boolean _returnQuotes=false;
+    private boolean _returnDelimiters=false;
+    private StringBuffer _token;
+    private boolean _hasToken=false;
+    private int _i=0;
+    private int _lastStart=0;
+    private boolean _double=true;
+    private boolean _single=true;
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters,
+                                 boolean returnQuotes)
+    {
+        super("");
+        _string=str;
+        if (delim!=null)
+            _delim=delim;
+        _returnDelimiters=returnDelimiters;
+        _returnQuotes=returnQuotes;
+
+        if (_delim.indexOf('\'')>=0 ||
+            _delim.indexOf('"')>=0)
+            throw new Error("Can't use quotes as delimiters: "+_delim);
+
+        _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim,
+                                 boolean returnDelimiters)
+    {
+        this(str,delim,returnDelimiters,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str,
+                                 String delim)
+    {
+        this(str,delim,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    public QuotedStringTokenizer(String str)
+    {
+        this(str,null,false,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreTokens()
+    {
+        // Already found a token
+        if (_hasToken)
+            return true;
+
+        _lastStart=_i;
+
+        int state=0;
+        boolean escape=false;
+        while (_i<_string.length())
+        {
+            char c=_string.charAt(_i++);
+
+            switch (state)
+            {
+              case 0: // Start
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                      {
+                          _token.append(c);
+                          return _hasToken=true;
+                      }
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                      _hasToken=true;
+                      state=1;
+                  }
+                  break;
+
+              case 1: // Token
+                  _hasToken=true;
+                  if(_delim.indexOf(c)>=0)
+                  {
+                      if (_returnDelimiters)
+                          _i--;
+                      return _hasToken;
+                  }
+                  else if (c=='\'' && _single)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=2;
+                  }
+                  else if (c=='\"' && _double)
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=3;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 2: // Single Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\'')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+
+              case 3: // Double Quote
+                  _hasToken=true;
+                  if (escape)
+                  {
+                      escape=false;
+                      _token.append(c);
+                  }
+                  else if (c=='\"')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      state=1;
+                  }
+                  else if (c=='\\')
+                  {
+                      if (_returnQuotes)
+                          _token.append(c);
+                      escape=true;
+                  }
+                  else
+                  {
+                      _token.append(c);
+                  }
+                  break;
+            }
+        }
+
+        return _hasToken;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken()
+        throws NoSuchElementException
+    {
+        if (!hasMoreTokens() || _token==null)
+            throw new NoSuchElementException();
+        String t=_token.toString();
+        _token.setLength(0);
+        _hasToken=false;
+        return t;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String nextToken(String delim)
+        throws NoSuchElementException
+    {
+        _delim=delim;
+        _i=_lastStart;
+        _token.setLength(0);
+        _hasToken=false;
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean hasMoreElements()
+    {
+        return hasMoreTokens();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object nextElement()
+        throws NoSuchElementException
+    {
+        return nextToken();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Not implemented.
+     */
+    @Override
+    public int countTokens()
+    {
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embedded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @param delim the delimiter to use to quote the string
+     * @return quoted string
+     */
+    public static String quoteIfNeeded(String s, String delim)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (c=='\\' || c=='"' || c=='\'' || Character.isWhitespace(c) || delim.indexOf(c)>=0)
+            {
+                StringBuffer b=new StringBuffer(s.length()+8);
+                quote(b,s);
+                return b.toString();
+            }
+        }
+
+        return s;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string.
+     * The string is quoted only if quoting is required due to
+     * embeded delimiters, quote characters or the
+     * empty string.
+     * @param s The string to quote.
+     * @return quoted string
+     */
+    public static String quote(String s)
+    {
+        if (s==null)
+            return null;
+        if (s.length()==0)
+            return "\"\"";
+
+        StringBuffer b=new StringBuffer(s.length()+8);
+        quote(b,s);
+        return b.toString();
+
+    }
+
+    private static final char[] escapes = new char[32];
+    static
+    {
+        Arrays.fill(escapes, (char)0xFFFF);
+        escapes['\b'] = 'b';
+        escapes['\t'] = 't';
+        escapes['\n'] = 'n';
+        escapes['\f'] = 'f';
+        escapes['\r'] = 'r';
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into an Appendable.
+     * The characters ", \, \n, \r, \t, \f and \b are escaped
+     * @param buffer The Appendable
+     * @param input The String to quote.
+     */
+    public static void quote(Appendable buffer, String input)
+    {
+        try
+        {
+            buffer.append('"');
+            for (int i = 0; i < input.length(); ++i)
+            {
+                char c = input.charAt(i);
+                if (c >= 32)
+                {
+                    if (c == '"' || c == '\\')
+                        buffer.append('\\');
+                    buffer.append(c);
+                }
+                else
+                {
+                    char escape = escapes[c];
+                    if (escape == 0xFFFF)
+                    {
+                        // Unicode escape
+                        buffer.append('\\').append('u').append('0').append('0');
+                        if (c < 0x10)
+                            buffer.append('0');
+                        buffer.append(Integer.toString(c, 16));
+                    }
+                    else
+                    {
+                        buffer.append('\\').append(escape);
+                    }
+                }
+            }
+            buffer.append('"');
+        }
+        catch (IOException x)
+        {
+            throw new RuntimeException(x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Quote a string into a StringBuffer only if needed.
+     * Quotes are forced if any delim characters are present.
+     *
+     * @param buf The StringBuffer
+     * @param s The String to quote.
+     * @param delim String of characters that must be quoted.
+     * @return true if quoted;
+     */
+    public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
+    {
+        for (int i=0;i<s.length();i++)
+        {
+            char c = s.charAt(i);
+            if (delim.indexOf(c)>=0)
+            {
+            	quote(buf,s);
+            	return true;
+            }
+        }
+
+        try
+        {
+            buf.append(s);
+            return false;
+        }
+        catch(IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static String unquoteOnly(String s)
+    {
+        return unquoteOnly(s, false);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Unquote a string, NOT converting unicode sequences
+     * @param s The string to unquote.
+     * @param lenient if true, will leave in backslashes that aren't valid escapes
+     * @return quoted string
+     */
+    public static String unquoteOnly(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                if (lenient && !isValidEscaping(c))
+                {
+                    b.append('\\');
+                }
+                b.append(c);
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString(); 
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String unquote(String s)
+    {
+        return unquote(s,false);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Unquote a string.
+     * @param s The string to unquote.
+     * @return quoted string
+     */
+    public static String unquote(String s, boolean lenient)
+    {
+        if (s==null)
+            return null;
+        if (s.length()<2)
+            return s;
+
+        char first=s.charAt(0);
+        char last=s.charAt(s.length()-1);
+        if (first!=last || (first!='"' && first!='\''))
+            return s;
+
+        StringBuilder b = new StringBuilder(s.length() - 2);
+        boolean escape=false;
+        for (int i=1;i<s.length()-1;i++)
+        {
+            char c = s.charAt(i);
+
+            if (escape)
+            {
+                escape=false;
+                switch (c)
+                {
+                    case 'n':
+                        b.append('\n');
+                        break;
+                    case 'r':
+                        b.append('\r');
+                        break;
+                    case 't':
+                        b.append('\t');
+                        break;
+                    case 'f':
+                        b.append('\f');
+                        break;
+                    case 'b':
+                        b.append('\b');
+                        break;
+                    case '\\':
+                        b.append('\\');
+                        break;
+                    case '/':
+                        b.append('/');
+                        break;
+                    case '"':
+                        b.append('"');
+                        break;
+                    case 'u':
+                        b.append((char)(
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
+                                (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
+                                )
+                        );
+                        break;
+                    default:
+                        if (lenient && !isValidEscaping(c))
+                        {
+                            b.append('\\');
+                        }
+                        b.append(c);
+                }
+            }
+            else if (c=='\\')
+            {
+                escape=true;
+            }
+            else
+            {
+                b.append(c);
+            }
+        }
+
+        return b.toString();
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Check that char c (which is preceded by a backslash) is a valid
+     * escape sequence.
+     * @param c
+     * @return
+     */
+    private static boolean isValidEscaping(char c)
+    {
+        return ((c == 'n') || (c == 'r') || (c == 't') || 
+                 (c == 'f') || (c == 'b') || (c == '\\') || 
+                 (c == '/') || (c == '"') || (c == 'u'));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle double quotes if true
+     */
+    public boolean getDouble()
+    {
+        return _double;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param d handle double quotes if true
+     */
+    public void setDouble(boolean d)
+    {
+        _double=d;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return handle single quotes if true
+     */
+    public boolean getSingle()
+    {
+        return _single;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param single handle single quotes if true
+     */
+    public void setSingle(boolean single)
+    {
+        _single=single;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ReadLineInputStream.java b/src/java/org/eclipse/jetty/util/ReadLineInputStream.java
new file mode 100644
index 0000000..be6abcc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ReadLineInputStream.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * ReadLineInputStream
+ *
+ * Read from an input stream, accepting CR/LF, LF or just CR.
+ */
+public class ReadLineInputStream extends BufferedInputStream
+{
+    boolean _seenCRLF;
+    boolean _skipLF;
+    
+    public ReadLineInputStream(InputStream in)
+    {
+        super(in);
+    }
+
+    public ReadLineInputStream(InputStream in, int size)
+    {
+        super(in,size);
+    }
+    
+    public String readLine() throws IOException
+    {
+        mark(buf.length);
+                
+        while (true)
+        {
+            int b=super.read();
+            
+            if (markpos < 0)
+                throw new IOException("Buffer size exceeded: no line terminator");
+            
+            if (b==-1)
+            {
+                int m=markpos;
+                markpos=-1;
+                if (pos>m)
+                    return new String(buf,m,pos-m,StringUtil.__UTF8_CHARSET);
+
+                return null;
+            }
+            
+            if (b=='\r')
+            {
+                int p=pos;
+                
+                // if we have seen CRLF before, hungrily consume LF
+                if (_seenCRLF && pos<count)
+                {
+                    if (buf[pos]=='\n')
+                        pos+=1;
+                }
+                else
+                    _skipLF=true;
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,p-m-1,StringUtil.__UTF8_CHARSET);
+            }
+            
+            if (b=='\n')
+            {
+                if (_skipLF)
+                {
+                    _skipLF=false;
+                    _seenCRLF=true;
+                    markpos++;
+                    continue;
+                }
+                int m=markpos;
+                markpos=-1;
+                return new String(buf,m,pos-m-1,StringUtil.__UTF8_CHARSET);
+            }
+        }
+    }
+
+    @Override
+    public synchronized int read() throws IOException
+    {
+        int b = super.read();
+        if (_skipLF)
+        {
+            _skipLF=false;
+            if (_seenCRLF && b=='\n')
+                b=super.read();
+        }
+        return b;
+    }
+
+    @Override
+    public synchronized int read(byte[] buf, int off, int len) throws IOException
+    {
+        if (_skipLF && len>0)
+        {
+            _skipLF=false;
+            if (_seenCRLF)
+            {
+                int b = super.read();
+                if (b==-1)
+                    return -1;
+                
+                if (b!='\n')
+                {
+                    buf[off]=(byte)(0xff&b);
+                    return 1+super.read(buf,off+1,len-1);
+                }
+            }
+        }
+        
+        return super.read(buf,off,len);
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/util/RolloverFileOutputStream.java b/src/java/org/eclipse/jetty/util/RolloverFileOutputStream.java
new file mode 100644
index 0000000..60f5da4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/RolloverFileOutputStream.java
@@ -0,0 +1,340 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util; 
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/** 
+ * RolloverFileOutputStream
+ * 
+ * This output stream puts content in a file that is rolled over every 24 hours.
+ * The filename must include the string "yyyy_mm_dd", which is replaced with the 
+ * actual date when creating and rolling over the file.
+ * 
+ * Old files are retained for a number of days before being deleted.
+ * 
+ * 
+ */
+public class RolloverFileOutputStream extends FilterOutputStream
+{
+    private static Timer __rollover;
+    
+    final static String YYYY_MM_DD="yyyy_mm_dd";
+    final static String ROLLOVER_FILE_DATE_FORMAT = "yyyy_MM_dd";
+    final static String ROLLOVER_FILE_BACKUP_FORMAT = "HHmmssSSS";
+    final static int ROLLOVER_FILE_RETAIN_DAYS = 31;
+
+    private RollTask _rollTask;
+    private SimpleDateFormat _fileBackupFormat;
+    private SimpleDateFormat _fileDateFormat;
+
+    private String _filename;
+    private File _file;
+    private boolean _append;
+    private int _retainDays;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename)
+        throws IOException
+    {
+        this(filename,true,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename, boolean append)
+        throws IOException
+    {
+        this(filename,append,ROLLOVER_FILE_RETAIN_DAYS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them.  0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays)
+        throws IOException
+    {
+        this(filename,append,retainDays,TimeZone.getDefault());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone)
+        throws IOException
+    {
+
+         this(filename,append,retainDays,zone,null,null);
+    }
+     
+    /* ------------------------------------------------------------ */
+    /**
+     * @param filename The filename must include the string "yyyy_mm_dd", 
+     * which is replaced with the actual date when creating and rolling over the file.
+     * @param append If true, existing files will be appended to.
+     * @param retainDays The number of days to retain files before deleting them. 0 to retain forever.
+     * @param dateFormat The format for the date file substitution. The default is "yyyy_MM_dd". 
+     * @param backupFormat The format for the file extension of backup files. The default is "HHmmssSSS". 
+     * @throws IOException
+     */
+    public RolloverFileOutputStream(String filename,
+                                    boolean append,
+                                    int retainDays,
+                                    TimeZone zone,
+                                    String dateFormat,
+                                    String backupFormat)
+        throws IOException
+    {
+        super(null);
+
+        if (dateFormat==null)
+            dateFormat=ROLLOVER_FILE_DATE_FORMAT;
+        _fileDateFormat = new SimpleDateFormat(dateFormat);
+        
+        if (backupFormat==null)
+            backupFormat=ROLLOVER_FILE_BACKUP_FORMAT;
+        _fileBackupFormat = new SimpleDateFormat(backupFormat);
+        
+        _fileBackupFormat.setTimeZone(zone);
+        _fileDateFormat.setTimeZone(zone);
+        
+        if (filename!=null)
+        {
+            filename=filename.trim();
+            if (filename.length()==0)
+                filename=null;
+        }
+        if (filename==null)
+            throw new IllegalArgumentException("Invalid filename");
+
+        _filename=filename;
+        _append=append;
+        _retainDays=retainDays;
+        setFile();
+        
+        synchronized(RolloverFileOutputStream.class)
+        {
+            if (__rollover==null)
+                __rollover=new Timer(RolloverFileOutputStream.class.getName(),true);
+            
+            _rollTask=new RollTask();
+
+             Calendar now = Calendar.getInstance();
+             now.setTimeZone(zone);
+
+             GregorianCalendar midnight =
+                 new GregorianCalendar(now.get(Calendar.YEAR),
+                         now.get(Calendar.MONTH),
+                         now.get(Calendar.DAY_OF_MONTH),
+                         23,0);
+             midnight.setTimeZone(zone);
+             midnight.add(Calendar.HOUR,1);
+             __rollover.scheduleAtFixedRate(_rollTask,midnight.getTime(),1000L*60*60*24);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getFilename()
+    {
+        return _filename;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String getDatedFilename()
+    {
+        if (_file==null)
+            return null;
+        return _file.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int getRetainDays()
+    {
+        return _retainDays;
+    }
+
+    /* ------------------------------------------------------------ */
+    private synchronized void setFile()
+        throws IOException
+    {
+        // Check directory
+        File file = new File(_filename);
+        _filename=file.getCanonicalPath();
+        file=new File(_filename);
+        File dir= new File(file.getParent());
+        if (!dir.isDirectory() || !dir.canWrite())
+            throw new IOException("Cannot write log directory "+dir);
+            
+        Date now=new Date();
+        
+        // Is this a rollover file?
+        String filename=file.getName();
+        int i=filename.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+        if (i>=0)
+        {
+            file=new File(dir,
+                          filename.substring(0,i)+
+                          _fileDateFormat.format(now)+
+                          filename.substring(i+YYYY_MM_DD.length()));
+        }
+            
+        if (file.exists()&&!file.canWrite())
+            throw new IOException("Cannot write log file "+file);
+
+        // Do we need to change the output stream?
+        if (out==null || !file.equals(_file))
+        {
+            // Yep
+            _file=file;
+            if (!_append && file.exists())
+                file.renameTo(new File(file.toString()+"."+_fileBackupFormat.format(now)));
+            OutputStream oldOut=out;
+            out=new FileOutputStream(file.toString(),_append);
+            if (oldOut!=null)
+                oldOut.close();
+            //if(log.isDebugEnabled())log.debug("Opened "+_file);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void removeOldFiles()
+    {
+        if (_retainDays>0)
+        {
+            long now = System.currentTimeMillis();
+            
+            File file= new File(_filename);
+            File dir = new File(file.getParent());
+            String fn=file.getName();
+            int s=fn.toLowerCase(Locale.ENGLISH).indexOf(YYYY_MM_DD);
+            if (s<0)
+                return;
+            String prefix=fn.substring(0,s);
+            String suffix=fn.substring(s+YYYY_MM_DD.length());
+
+            String[] logList=dir.list();
+            for (int i=0;i<logList.length;i++)
+            {
+                fn = logList[i];
+                if(fn.startsWith(prefix)&&fn.indexOf(suffix,prefix.length())>=0)
+                {        
+                    File f = new File(dir,fn);
+                    long date = f.lastModified();
+                    if ( ((now-date)/(1000*60*60*24))>_retainDays)
+                        f.delete();   
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf)
+            throws IOException
+     {
+            out.write (buf);
+     }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void write (byte[] buf, int off, int len)
+            throws IOException
+     {
+            out.write (buf, off, len);
+     }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public void close()
+        throws IOException
+    {
+        synchronized(RolloverFileOutputStream.class)
+        {
+            try{super.close();}
+            finally
+            {
+                out=null;
+                _file=null;
+            }
+
+            _rollTask.cancel(); 
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class RollTask extends TimerTask
+    {
+        @Override
+        public void run()
+        {
+            try
+            {
+                RolloverFileOutputStream.this.setFile();
+                RolloverFileOutputStream.this.removeOldFiles();
+
+            }
+            catch(IOException e)
+            {
+                // Cannot log this exception to a LOG, as RolloverFOS can be used by logging
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Scanner.java b/src/java/org/eclipse/jetty/util/Scanner.java
new file mode 100644
index 0000000..cc92ae6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Scanner.java
@@ -0,0 +1,746 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/**
+ * Scanner
+ * 
+ * Utility for scanning a directory for added, removed and changed
+ * files and reporting these events via registered Listeners.
+ *
+ */
+public class Scanner extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(Scanner.class);
+    private static int __scannerId=0;
+    private int _scanInterval;
+    private int _scanCount = 0;
+    private final List<Listener> _listeners = new ArrayList<Listener>();
+    private final Map<String,TimeNSize> _prevScan = new HashMap<String,TimeNSize> ();
+    private final Map<String,TimeNSize> _currentScan = new HashMap<String,TimeNSize> ();
+    private FilenameFilter _filter;
+    private final List<File> _scanDirs = new ArrayList<File>();
+    private volatile boolean _running = false;
+    private boolean _reportExisting = true;
+    private boolean _reportDirs = true;
+    private Timer _timer;
+    private TimerTask _task;
+    private int _scanDepth=0;
+    
+    public enum Notification { ADDED, CHANGED, REMOVED };
+    private final Map<String,Notification> _notifications = new HashMap<String,Notification>();
+
+    static class TimeNSize
+    {
+        final long _lastModified;
+        final long _size;
+        
+        public TimeNSize(long lastModified, long size)
+        {
+            _lastModified = lastModified;
+            _size = size;
+        }
+        
+        @Override
+        public int hashCode()
+        {
+            return (int)_lastModified^(int)_size;
+        }
+        
+        @Override
+        public boolean equals(Object o)
+        {
+            if (o instanceof TimeNSize)
+            {
+                TimeNSize tns = (TimeNSize)o;
+                return tns._lastModified==_lastModified && tns._size==_size;
+            }
+            return false;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return "[lm="+_lastModified+",s="+_size+"]";
+        }
+    }
+    
+    /**
+     * Listener
+     * 
+     * Marker for notifications re file changes.
+     */
+    public interface Listener
+    {
+    }
+
+    public interface ScanListener extends Listener
+    {
+        public void scan();
+    }
+    
+    public interface DiscreteListener extends Listener
+    {
+        public void fileChanged (String filename) throws Exception;
+        public void fileAdded (String filename) throws Exception;
+        public void fileRemoved (String filename) throws Exception;
+    }
+    
+    
+    public interface BulkListener extends Listener
+    {
+        public void filesChanged (List<String> filenames) throws Exception;
+    }
+
+    /**
+     * Listener that notifies when a scan has started and when it has ended.
+     */
+    public interface ScanCycleListener extends Listener
+    {
+        public void scanStarted(int cycle) throws Exception;
+        public void scanEnded(int cycle) throws Exception;
+    }
+
+    /**
+     * 
+     */
+    public Scanner ()
+    {       
+    }
+
+    /**
+     * Get the scan interval
+     * @return interval between scans in seconds
+     */
+    public int getScanInterval()
+    {
+        return _scanInterval;
+    }
+
+    /**
+     * Set the scan interval
+     * @param scanInterval pause between scans in seconds, or 0 for no scan after the initial scan.
+     */
+    public synchronized void setScanInterval(int scanInterval)
+    {
+        _scanInterval = scanInterval;
+        schedule();
+    }
+
+    /**
+     * Set the location of the directory to scan.
+     * @param dir
+     * @deprecated use setScanDirs(List dirs) instead
+     */
+    @Deprecated
+    public void setScanDir (File dir)
+    {
+        _scanDirs.clear(); 
+        _scanDirs.add(dir);
+    }
+
+    /**
+     * Get the location of the directory to scan
+     * @return the first directory (of {@link #getScanDirs()} being scanned)
+     * @deprecated use getScanDirs() instead
+     */
+    @Deprecated
+    public File getScanDir ()
+    {
+        return (_scanDirs==null?null:(File)_scanDirs.get(0));
+    }
+
+    public void setScanDirs (List<File> dirs)
+    {
+        _scanDirs.clear(); 
+        _scanDirs.addAll(dirs);
+    }
+    
+    public synchronized void addScanDir( File dir )
+    {
+        _scanDirs.add( dir );
+    }
+    
+    public List<File> getScanDirs ()
+    {
+        return Collections.unmodifiableList(_scanDirs);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param recursive True if scanning is recursive
+     * @see  #setScanDepth(int)
+     */
+    public void setRecursive (boolean recursive)
+    {
+        _scanDepth=recursive?-1:0;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if scanning is fully recursive (scandepth==-1)
+     * @see #getScanDepth()
+     */
+    public boolean getRecursive ()
+    {
+        return _scanDepth==-1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the scanDepth.
+     * @return the scanDepth
+     */
+    public int getScanDepth()
+    {
+        return _scanDepth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the scanDepth.
+     * @param scanDepth the scanDepth to set
+     */
+    public void setScanDepth(int scanDepth)
+    {
+        _scanDepth = scanDepth;
+    }
+
+    /**
+     * Apply a filter to files found in the scan directory.
+     * Only files matching the filter will be reported as added/changed/removed.
+     * @param filter
+     */
+    public void setFilenameFilter (FilenameFilter filter)
+    {
+        _filter = filter;
+    }
+
+    /**
+     * Get any filter applied to files in the scan dir.
+     * @return the filename filter
+     */
+    public FilenameFilter getFilenameFilter ()
+    {
+        return _filter;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Whether or not an initial scan will report all files as being
+     * added.
+     * @param reportExisting if true, all files found on initial scan will be 
+     * reported as being added, otherwise not
+     */
+    public void setReportExistingFilesOnStartup (boolean reportExisting)
+    {
+        _reportExisting = reportExisting;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getReportExistingFilesOnStartup()
+    {
+        return _reportExisting;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set if found directories should be reported.
+     * @param dirs
+     */
+    public void setReportDirs(boolean dirs)
+    {
+        _reportDirs=dirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean getReportDirs()
+    {
+        return _reportDirs;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an added/removed/changed listener
+     * @param listener
+     */
+    public synchronized void addListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.add(listener);   
+    }
+
+
+
+    /**
+     * Remove a registered listener
+     * @param listener the Listener to be removed
+     */
+    public synchronized void removeListener (Listener listener)
+    {
+        if (listener == null)
+            return;
+        _listeners.remove(listener);    
+    }
+
+
+    /**
+     * Start the scanning action.
+     */
+    @Override
+    public synchronized void doStart()
+    {
+        if (_running)
+            return;
+
+        _running = true;
+
+        if (_reportExisting)
+        {
+            // if files exist at startup, report them
+            scan();
+            scan(); // scan twice so files reported as stable
+        }
+        else
+        {
+            //just register the list of existing files and only report changes
+            scanFiles();
+            _prevScan.putAll(_currentScan);
+        }
+        schedule();
+    }
+
+    public TimerTask newTimerTask ()
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run() { scan(); }
+        };
+    }
+
+    public Timer newTimer ()
+    {
+        return new Timer("Scanner-"+__scannerId++, true);
+    }
+    
+    public void schedule ()
+    {  
+        if (_running)
+        {
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            if (getScanInterval() > 0)
+            {
+                _timer = newTimer();
+                _task = newTimerTask();
+                _timer.schedule(_task, 1010L*getScanInterval(),1010L*getScanInterval());
+            }
+        }
+    }
+    /**
+     * Stop the scanning.
+     */
+    @Override
+    public synchronized void doStop()
+    {
+        if (_running)
+        {
+            _running = false; 
+            if (_timer!=null)
+                _timer.cancel();
+            if (_task!=null)
+                _task.cancel();
+            _task=null;
+            _timer=null;
+        }
+    }
+
+    /**
+     * Perform a pass of the scanner and report changes
+     */
+    public synchronized void scan ()
+    {
+        reportScanStart(++_scanCount);
+        scanFiles();
+        reportDifferences(_currentScan, _prevScan);
+        _prevScan.clear();
+        _prevScan.putAll(_currentScan);
+        reportScanEnd(_scanCount);
+        
+        for (Listener l : _listeners)
+        {
+            try
+            {
+                if (l instanceof ScanListener)
+                    ((ScanListener)l).scan();
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+            }
+            catch (Error e)
+            {
+                LOG.warn(e);
+            }
+        }
+    }
+
+    /**
+     * Recursively scan all files in the designated directories.
+     */
+    public synchronized void scanFiles ()
+    {
+        if (_scanDirs==null)
+            return;
+        
+        _currentScan.clear();
+        Iterator<File> itor = _scanDirs.iterator();
+        while (itor.hasNext())
+        {
+            File dir = itor.next();
+            
+            if ((dir != null) && (dir.exists()))
+                try
+                {
+                    scanFile(dir.getCanonicalFile(), _currentScan,0);
+                }
+                catch (IOException e)
+                {
+                    LOG.warn("Error scanning files.", e);
+                }
+        }
+    }
+
+
+    /**
+     * Report the adds/changes/removes to the registered listeners
+     * 
+     * @param currentScan the info from the most recent pass
+     * @param oldScan info from the previous pass
+     */
+    public synchronized void reportDifferences (Map<String,TimeNSize> currentScan, Map<String,TimeNSize> oldScan) 
+    {
+        // scan the differences and add what was found to the map of notifications:
+
+        Set<String> oldScanKeys = new HashSet<String>(oldScan.keySet());
+        
+        // Look for new and changed files
+        for (Map.Entry<String, TimeNSize> entry: currentScan.entrySet())
+        {
+            String file = entry.getKey(); 
+            if (!oldScanKeys.contains(file))
+            {
+                Notification old=_notifications.put(file,Notification.ADDED);
+                if (old!=null)
+                { 
+                    switch(old)
+                    {
+                        case REMOVED: 
+                        case CHANGED:
+                            _notifications.put(file,Notification.CHANGED);
+                    }
+                }
+            }
+            else if (!oldScan.get(file).equals(currentScan.get(file)))
+            {
+                Notification old=_notifications.put(file,Notification.CHANGED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.put(file,Notification.ADDED);
+                    }
+                }
+            }
+        }
+        
+        // Look for deleted files
+        for (String file : oldScan.keySet())
+        {
+            if (!currentScan.containsKey(file))
+            {
+                Notification old=_notifications.put(file,Notification.REMOVED);
+                if (old!=null)
+                {
+                    switch(old)
+                    {
+                        case ADDED:
+                            _notifications.remove(file);
+                    }
+                }
+            }
+        }
+        
+        if (LOG.isDebugEnabled())
+            LOG.debug("scanned "+_scanDirs+": "+_notifications);
+                
+        // Process notifications
+        // Only process notifications that are for stable files (ie same in old and current scan).
+        List<String> bulkChanges = new ArrayList<String>();
+        for (Iterator<Entry<String,Notification>> iter = _notifications.entrySet().iterator();iter.hasNext();)
+        {
+            Entry<String,Notification> entry=iter.next();
+            String file=entry.getKey();
+            
+            // Is the file stable?
+            if (oldScan.containsKey(file))
+            {
+                if (!oldScan.get(file).equals(currentScan.get(file)))
+                    continue;
+            }
+            else if (currentScan.containsKey(file))
+                continue;
+                            
+            // File is stable so notify
+            Notification notification=entry.getValue();
+            iter.remove();
+            bulkChanges.add(file);
+            switch(notification)
+            {
+                case ADDED:
+                    reportAddition(file);
+                    break;
+                case CHANGED:
+                    reportChange(file);
+                    break;
+                case REMOVED:
+                    reportRemoval(file);
+                    break;
+            }
+        }
+        if (!bulkChanges.isEmpty())
+            reportBulkChanges(bulkChanges);
+    }
+
+
+    /**
+     * Get last modified time on a single file or recurse if
+     * the file is a directory. 
+     * @param f file or directory
+     * @param scanInfoMap map of filenames to last modified times
+     */
+    private void scanFile (File f, Map<String,TimeNSize> scanInfoMap, int depth)
+    {
+        try
+        {
+            if (!f.exists())
+                return;
+
+            if (f.isFile() || depth>0&& _reportDirs && f.isDirectory())
+            {
+                if ((_filter == null) || ((_filter != null) && _filter.accept(f.getParentFile(), f.getName())))
+                {
+                    String name = f.getCanonicalPath();
+                    scanInfoMap.put(name, new TimeNSize(f.lastModified(),f.length()));
+                }
+            }
+            
+            // If it is a directory, scan if it is a known directory or the depth is OK.
+            if (f.isDirectory() && (depth<_scanDepth || _scanDepth==-1 || _scanDirs.contains(f)))
+            {
+                File[] files = f.listFiles();
+                if (files != null)
+                {
+                    for (int i=0;i<files.length;i++)
+                        scanFile(files[i], scanInfoMap,depth+1);
+                }
+                else
+                    LOG.warn("Error listing files in directory {}", f);
+                    
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.warn("Error scanning watched files", e);
+        }
+    }
+
+    private void warn(Object listener,String filename,Throwable th)
+    {
+        LOG.warn(listener+" failed on '"+filename, th);
+    }
+
+    /**
+     * Report a file addition to the registered FileAddedListeners
+     * @param filename
+     */
+    private void reportAddition (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileAdded(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file removal to the FileRemovedListeners
+     * @param filename
+     */
+    private void reportRemoval (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Object l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileRemoved(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+
+
+    /**
+     * Report a file change to the FileChangedListeners
+     * @param filename
+     */
+    private void reportChange (String filename)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof DiscreteListener)
+                    ((DiscreteListener)l).fileChanged(filename);
+            }
+            catch (Exception e)
+            {
+                warn(l,filename,e);
+            }
+            catch (Error e)
+            {
+                warn(l,filename,e);
+            }
+        }
+    }
+    
+    private void reportBulkChanges (List<String> filenames)
+    {
+        Iterator<Listener> itor = _listeners.iterator();
+        while (itor.hasNext())
+        {
+            Listener l = itor.next();
+            try
+            {
+                if (l instanceof BulkListener)
+                    ((BulkListener)l).filesChanged(filenames);
+            }
+            catch (Exception e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+            catch (Error e)
+            {
+                warn(l,filenames.toString(),e);
+            }
+        }
+    }
+    
+    /**
+     * signal any scan cycle listeners that a scan has started
+     */
+    private void reportScanStart(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanStarted(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan start for cycle " + cycle, e);
+            }
+        }
+    }
+
+    /**
+     * sign
+     */
+    private void reportScanEnd(int cycle)
+    {
+        for (Listener listener : _listeners)
+        {
+            try
+            {
+                if (listener instanceof ScanCycleListener)
+                {
+                    ((ScanCycleListener)listener).scanEnded(cycle);
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(listener + " failed on scan end for cycle " + cycle, e);
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/StringMap.java b/src/java/org/eclipse/jetty/util/StringMap.java
new file mode 100644
index 0000000..bf84b23
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/StringMap.java
@@ -0,0 +1,695 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.Externalizable;
+import java.util.AbstractMap;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/* ------------------------------------------------------------ */
+/** Map implementation Optimized for Strings keys..
+ * This String Map has been optimized for mapping small sets of
+ * Strings where the most frequently accessed Strings have been put to
+ * the map first.
+ *
+ * It also has the benefit that it can look up entries by substring or
+ * sections of char and byte arrays.  This can prevent many String
+ * objects from being created just to look up in the map.
+ *
+ * This map is NOT synchronized.
+ */
+public class StringMap extends AbstractMap implements Externalizable
+{
+    public static final boolean CASE_INSENSTIVE=true;
+    protected static final int __HASH_WIDTH=17;
+    
+    /* ------------------------------------------------------------ */
+    protected int _width=__HASH_WIDTH;
+    protected Node _root=new Node();
+    protected boolean _ignoreCase=false;
+    protected NullEntry _nullEntry=null;
+    protected Object _nullValue=null;
+    protected HashSet _entrySet=new HashSet(3);
+    protected Set _umEntrySet=Collections.unmodifiableSet(_entrySet);
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     */
+    public StringMap()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param ignoreCase 
+     */
+    public StringMap(boolean ignoreCase)
+    {
+        this();
+        _ignoreCase=ignoreCase;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor. 
+     * @param ignoreCase 
+     * @param width Width of hash tables, larger values are faster but
+     * use more memory.
+     */
+    public StringMap(boolean ignoreCase,int width)
+    {
+        this();
+        _ignoreCase=ignoreCase;
+        _width=width;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Set the ignoreCase attribute.
+     * @param ic If true, the map is case insensitive for keys.
+     */
+    public void setIgnoreCase(boolean ic)
+    {
+        if (_root._children!=null)
+            throw new IllegalStateException("Must be set before first put");
+        _ignoreCase=ic;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIgnoreCase()
+    {
+        return _ignoreCase;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the hash width.
+     * @param width Width of hash tables, larger values are faster but
+     * use more memory.
+     */
+    public void setWidth(int width)
+    {
+        _width=width;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getWidth()
+    {
+        return _width;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object put(Object key, Object value)
+    {
+        if (key==null)
+            return put(null,value);
+        return put(key.toString(),value);
+    }
+        
+    /* ------------------------------------------------------------ */
+    public Object put(String key, Object value)
+    {
+        if (key==null)
+        {
+            Object oldValue=_nullValue;
+            _nullValue=value;
+            if (_nullEntry==null)
+            {   
+                _nullEntry=new NullEntry();
+                _entrySet.add(_nullEntry);
+            }
+            return oldValue;
+        }
+        
+        Node node = _root;
+        int ni=-1;
+        Node prev = null;
+        Node parent = null;
+
+        // look for best match
+    charLoop:
+        for (int i=0;i<key.length();i++)
+        {
+            char c=key.charAt(i);
+            
+            // Advance node
+            if (ni==-1)
+            {
+                parent=node;
+                prev=null;
+                ni=0;
+                node=(node._children==null)?null:node._children[c%_width];
+            }
+            
+            // Loop through a node chain at the same level
+            while (node!=null) 
+            {
+                // If it is a matching node, goto next char
+                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+                {
+                    prev=null;
+                    ni++;
+                    if (ni==node._char.length)
+                        ni=-1;
+                    continue charLoop;
+                }
+
+                // no char match
+                // if the first char,
+                if (ni==0)
+                {
+                    // look along the chain for a char match
+                    prev=node;
+                    node=node._next;
+                }
+                else
+                {
+                    // Split the current node!
+                    node.split(this,ni);
+                    i--;
+                    ni=-1;
+                    continue charLoop;
+                }
+            }
+
+            // We have run out of nodes, so as this is a put, make one
+            node = new Node(_ignoreCase,key,i);
+
+            if (prev!=null) // add to end of chain
+                prev._next=node;
+            else if (parent!=null) // add new child
+            {
+                if (parent._children==null)
+                    parent._children=new Node[_width];
+                parent._children[c%_width]=node;
+                int oi=node._ochar[0]%_width;
+                if (node._ochar!=null && node._char[0]%_width!=oi)
+                {
+                    if (parent._children[oi]==null)
+                        parent._children[oi]=node;
+                    else
+                    {
+                        Node n=parent._children[oi];
+                        while(n._next!=null)
+                            n=n._next;
+                        n._next=node;
+                    }
+                }
+            }
+            else // this is the root.
+                _root=node;
+            break;
+        }
+        
+        // Do we have a node
+        if (node!=null)
+        {
+            // Split it if we are in the middle
+            if(ni>0)
+                node.split(this,ni);
+        
+            Object old = node._value;
+            node._key=key;
+            node._value=value;
+            _entrySet.add(node);
+            return old;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object get(Object key)
+    {
+        if (key==null)
+            return _nullValue;
+        if (key instanceof String)
+            return get((String)key);
+        return get(key.toString());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object get(String key)
+    {
+        if (key==null)
+            return _nullValue;
+        
+        Map.Entry entry = getEntry(key,0,key.length());
+        if (entry==null)
+            return null;
+        return entry.getValue();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get a map entry by substring key.
+     * @param key String containing the key
+     * @param offset Offset of the key within the String.
+     * @param length The length of the key 
+     * @return The Map.Entry for the key or null if the key is not in
+     * the map.
+     */
+    public Map.Entry getEntry(String key,int offset, int length)
+    {
+        if (key==null)
+            return _nullEntry;
+        
+        Node node = _root;
+        int ni=-1;
+
+        // look for best match
+    charLoop:
+        for (int i=0;i<length;i++)
+        {
+            char c=key.charAt(offset+i);
+
+            // Advance node
+            if (ni==-1)
+            {
+                ni=0;
+                node=(node._children==null)?null:node._children[c%_width];
+            }
+            
+            // Look through the node chain
+            while (node!=null) 
+            {
+                // If it is a matching node, goto next char
+                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+                {
+                    ni++;
+                    if (ni==node._char.length)
+                        ni=-1;
+                    continue charLoop;
+                }
+
+                // No char match, so if mid node then no match at all.
+                if (ni>0) return null;
+
+                // try next in chain
+                node=node._next;                
+            }
+            return null;
+        }
+        
+        if (ni>0) return null;
+        if (node!=null && node._key==null)
+            return null;
+        return node;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get a map entry by char array key.
+     * @param key char array containing the key
+     * @param offset Offset of the key within the array.
+     * @param length The length of the key 
+     * @return The Map.Entry for the key or null if the key is not in
+     * the map.
+     */
+    public Map.Entry getEntry(char[] key,int offset, int length)
+    {
+        if (key==null)
+            return _nullEntry;
+        
+        Node node = _root;
+        int ni=-1;
+
+        // look for best match
+    charLoop:
+        for (int i=0;i<length;i++)
+        {
+            char c=key[offset+i];
+
+            // Advance node
+            if (ni==-1)
+            {
+                ni=0;
+                node=(node._children==null)?null:node._children[c%_width];
+            }
+            
+            // While we have a node to try
+            while (node!=null) 
+            {
+                // If it is a matching node, goto next char
+                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+                {
+                    ni++;
+                    if (ni==node._char.length)
+                        ni=-1;
+                    continue charLoop;
+                }
+
+                // No char match, so if mid node then no match at all.
+                if (ni>0) return null;
+
+                // try next in chain
+                node=node._next;                
+            }
+            return null;
+        }
+        
+        if (ni>0) return null;
+        if (node!=null && node._key==null)
+            return null;
+        return node;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get a map entry by byte array key, using as much of the passed key as needed for a match.
+     * A simple 8859-1 byte to char mapping is assumed.
+     * @param key char array containing the key
+     * @param offset Offset of the key within the array.
+     * @param maxLength The length of the key 
+     * @return The Map.Entry for the key or null if the key is not in
+     * the map.
+     */
+    public Map.Entry getBestEntry(byte[] key,int offset, int maxLength)
+    {
+        if (key==null)
+            return _nullEntry;
+        
+        Node node = _root;
+        int ni=-1;
+
+        // look for best match
+    charLoop:
+        for (int i=0;i<maxLength;i++)
+        {
+            char c=(char)key[offset+i];
+
+            // Advance node
+            if (ni==-1)
+            {
+                ni=0;
+                
+                Node child = (node._children==null)?null:node._children[c%_width];
+                
+                if (child==null && i>0)
+                    return node; // This is the best match
+                node=child;           
+            }
+            
+            // While we have a node to try
+            while (node!=null) 
+            {
+                // If it is a matching node, goto next char
+                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+                {
+                    ni++;
+                    if (ni==node._char.length)
+                        ni=-1;
+                    continue charLoop;
+                }
+
+                // No char match, so if mid node then no match at all.
+                if (ni>0) return null;
+
+                // try next in chain
+                node=node._next;                
+            }
+            return null;
+        }
+        
+        if (ni>0) return null;
+        if (node!=null && node._key==null)
+            return null;
+        return node;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object remove(Object key)
+    {
+        if (key==null)
+            return remove(null);
+        return remove(key.toString());
+    }
+    
+    /* ------------------------------------------------------------ */
+    public Object remove(String key)
+    {
+        if (key==null)
+        {
+            Object oldValue=_nullValue;
+            if (_nullEntry!=null)
+            {
+                _entrySet.remove(_nullEntry);   
+                _nullEntry=null;
+                _nullValue=null;
+            }
+            return oldValue;
+        }
+        
+        Node node = _root;
+        int ni=-1;
+
+        // look for best match
+    charLoop:
+        for (int i=0;i<key.length();i++)
+        {
+            char c=key.charAt(i);
+
+            // Advance node
+            if (ni==-1)
+            {
+                ni=0;
+                node=(node._children==null)?null:node._children[c%_width];
+            }
+            
+            // While we have a node to try
+            while (node!=null) 
+            {
+                // If it is a matching node, goto next char
+                if (node._char[ni]==c || _ignoreCase&&node._ochar[ni]==c)
+                {
+                    ni++;
+                    if (ni==node._char.length)
+                        ni=-1;
+                    continue charLoop;
+                }
+
+                // No char match, so if mid node then no match at all.
+                if (ni>0) return null;
+
+                // try next in chain
+                node=node._next;         
+            }
+            return null;
+        }
+
+        if (ni>0) return null;
+        if (node!=null && node._key==null)
+            return null;
+        
+        Object old = node._value;
+        _entrySet.remove(node);
+        node._value=null;
+        node._key=null;
+        
+        return old; 
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Set entrySet()
+    {
+        return _umEntrySet;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public int size()
+    {
+        return _entrySet.size();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isEmpty()
+    {
+        return _entrySet.isEmpty();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean containsKey(Object key)
+    {
+        if (key==null)
+            return _nullEntry!=null;
+        return
+            getEntry(key.toString(),0,key==null?0:key.toString().length())!=null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void clear()
+    {
+        _root=new Node();
+        _nullEntry=null;
+        _nullValue=null;
+        _entrySet.clear();
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class Node implements Map.Entry
+    {
+        char[] _char;
+        char[] _ochar;
+        Node _next;
+        Node[] _children;
+        String _key;
+        Object _value;
+        
+        Node(){}
+        
+        Node(boolean ignoreCase,String s, int offset)
+        {
+            int l=s.length()-offset;
+            _char=new char[l];
+            _ochar=new char[l];
+            for (int i=0;i<l;i++)
+            {
+                char c=s.charAt(offset+i);
+                _char[i]=c;
+                if (ignoreCase)
+                {
+                    char o=c;
+                    if (Character.isUpperCase(c))
+                        o=Character.toLowerCase(c);
+                    else if (Character.isLowerCase(c))
+                        o=Character.toUpperCase(c);
+                    _ochar[i]=o;
+                }
+            }
+        }
+
+        Node split(StringMap map,int offset)
+        {
+            Node split = new Node();
+            int sl=_char.length-offset;
+            
+            char[] tmp=this._char;
+            this._char=new char[offset];
+            split._char = new char[sl];
+            System.arraycopy(tmp,0,this._char,0,offset);
+            System.arraycopy(tmp,offset,split._char,0,sl);
+
+            if (this._ochar!=null)
+            {
+                tmp=this._ochar;
+                this._ochar=new char[offset];
+                split._ochar = new char[sl];
+                System.arraycopy(tmp,0,this._ochar,0,offset);
+                System.arraycopy(tmp,offset,split._ochar,0,sl);
+            }
+            
+            split._key=this._key;
+            split._value=this._value;
+            this._key=null;
+            this._value=null;
+            if (map._entrySet.remove(this))
+                map._entrySet.add(split);
+
+            split._children=this._children;            
+            this._children=new Node[map._width];
+            this._children[split._char[0]%map._width]=split;
+            if (split._ochar!=null && this._children[split._ochar[0]%map._width]!=split)
+                this._children[split._ochar[0]%map._width]=split;
+
+            return split;
+        }
+        
+        public Object getKey(){return _key;}
+        public Object getValue(){return _value;}
+        public Object setValue(Object o){Object old=_value;_value=o;return old;}
+        @Override
+        public String toString()
+        {
+            StringBuilder buf=new StringBuilder();
+            toString(buf);
+            return buf.toString();
+        }
+
+        private void toString(StringBuilder buf)
+        {
+            buf.append("{[");
+            if (_char==null)
+                buf.append('-');
+            else
+                for (int i=0;i<_char.length;i++)
+                    buf.append(_char[i]);
+            buf.append(':');
+            buf.append(_key);
+            buf.append('=');
+            buf.append(_value);
+            buf.append(']');
+            if (_children!=null)
+            {
+                for (int i=0;i<_children.length;i++)
+                {
+                    buf.append('|');
+                    if (_children[i]!=null)
+                        _children[i].toString(buf);
+                    else
+                        buf.append("-");
+                }
+            }
+            buf.append('}');
+            if (_next!=null)
+            {
+                buf.append(",\n");
+                _next.toString(buf);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class NullEntry implements Map.Entry
+    {
+        public Object getKey(){return null;}
+        public Object getValue(){return _nullValue;}
+        public Object setValue(Object o)
+            {Object old=_nullValue;_nullValue=o;return old;}
+        @Override
+        public String toString(){return "[:null="+_nullValue+"]";}
+    }
+
+    /* ------------------------------------------------------------ */
+    public void writeExternal(java.io.ObjectOutput out)
+        throws java.io.IOException
+    {
+        HashMap map = new HashMap(this);
+        out.writeBoolean(_ignoreCase);
+        out.writeObject(map);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void readExternal(java.io.ObjectInput in)
+        throws java.io.IOException, ClassNotFoundException
+    {
+        boolean ic=in.readBoolean();
+        HashMap map = (HashMap)in.readObject();
+        setIgnoreCase(ic);
+        this.putAll(map);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/StringUtil.java b/src/java/org/eclipse/jetty/util/StringUtil.java
new file mode 100644
index 0000000..b8147be
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/StringUtil.java
@@ -0,0 +1,504 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/** Fast String Utilities.
+ *
+ * These string utilities provide both conveniance methods and
+ * performance improvements over most standard library versions. The
+ * main aim of the optimizations is to avoid object creation unless
+ * absolutely required.
+ *
+ * 
+ */
+public class StringUtil
+{
+    private static final Logger LOG = Log.getLogger(StringUtil.class);
+    
+    public static final String ALL_INTERFACES="0.0.0.0";
+    public static final String CRLF="\015\012";
+    public static final String __LINE_SEPARATOR=
+        System.getProperty("line.separator","\n");
+       
+    public static final String __ISO_8859_1="ISO-8859-1";
+    public final static String __UTF8="UTF-8";
+    public final static String __UTF8Alt="UTF8";
+    public final static String __UTF16="UTF-16";
+    
+    public final static Charset __UTF8_CHARSET;
+    public final static Charset __ISO_8859_1_CHARSET;
+    
+    static
+    {
+        __UTF8_CHARSET=Charset.forName(__UTF8);
+        __ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
+    }
+    
+    private static char[] lowercases = {
+          '\000','\001','\002','\003','\004','\005','\006','\007',
+          '\010','\011','\012','\013','\014','\015','\016','\017',
+          '\020','\021','\022','\023','\024','\025','\026','\027',
+          '\030','\031','\032','\033','\034','\035','\036','\037',
+          '\040','\041','\042','\043','\044','\045','\046','\047',
+          '\050','\051','\052','\053','\054','\055','\056','\057',
+          '\060','\061','\062','\063','\064','\065','\066','\067',
+          '\070','\071','\072','\073','\074','\075','\076','\077',
+          '\100','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\133','\134','\135','\136','\137',
+          '\140','\141','\142','\143','\144','\145','\146','\147',
+          '\150','\151','\152','\153','\154','\155','\156','\157',
+          '\160','\161','\162','\163','\164','\165','\166','\167',
+          '\170','\171','\172','\173','\174','\175','\176','\177' };
+
+    /* ------------------------------------------------------------ */
+    /**
+     * fast lower case conversion. Only works on ascii (not unicode)
+     * @param s the string to convert
+     * @return a lower case version of s
+     */
+    public static String asciiToLowerCase(String s)
+    {
+        char[] c = null;
+        int i=s.length();
+
+        // look for first conversion
+        while (i-->0)
+        {
+            char c1=s.charAt(i);
+            if (c1<=127)
+            {
+                char c2=lowercases[c1];
+                if (c1!=c2)
+                {
+                    c=s.toCharArray();
+                    c[i]=c2;
+                    break;
+                }
+            }
+        }
+
+        while (i-->0)
+        {
+            if(c[i]<=127)
+                c[i] = lowercases[c[i]];
+        }
+        
+        return c==null?s:new String(c);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static boolean startsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+        
+        if (s==null || s.length()<w.length())
+            return false;
+        
+        for (int i=0;i<w.length();i++)
+        {
+            char c1=s.charAt(i);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean endsWithIgnoreCase(String s,String w)
+    {
+        if (w==null)
+            return true;
+
+        if (s==null)
+            return false;
+            
+        int sl=s.length();
+        int wl=w.length();
+        
+        if (sl<wl)
+            return false;
+        
+        for (int i=wl;i-->0;)
+        {
+            char c1=s.charAt(--sl);
+            char c2=w.charAt(i);
+            if (c1!=c2)
+            {
+                if (c1<=127)
+                    c1=lowercases[c1];
+                if (c2<=127)
+                    c2=lowercases[c2];
+                if (c1!=c2)
+                    return false;
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * returns the next index of a character from the chars string
+     */
+    public static int indexFrom(String s,String chars)
+    {
+        for (int i=0;i<s.length();i++)
+           if (chars.indexOf(s.charAt(i))>=0)
+              return i;
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * replace substrings within string.
+     */
+    public static String replace(String s, String sub, String with)
+    {
+        int c=0;
+        int i=s.indexOf(sub,c);
+        if (i == -1)
+            return s;
+    
+        StringBuilder buf = new StringBuilder(s.length()+with.length());
+
+        do
+        {
+            buf.append(s.substring(c,i));
+            buf.append(with);
+            c=i+sub.length();
+        } while ((i=s.indexOf(sub,c))!=-1);
+
+        if (c<s.length())
+            buf.append(s.substring(c,s.length()));
+
+        return buf.toString();
+        
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Remove single or double quotes.
+     */
+    public static String unquote(String s)
+    {
+        return QuotedStringTokenizer.unquote(s);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Append substring to StringBuilder 
+     * @param buf StringBuilder to append to
+     * @param s String to append from
+     * @param offset The offset of the substring
+     * @param length The length of the substring
+     */
+    public static void append(StringBuilder buf,
+                              String s,
+                              int offset,
+                              int length)
+    {
+        synchronized(buf)
+        {
+            int end=offset+length;
+            for (int i=offset; i<end;i++)
+            {
+                if (i>=s.length())
+                    break;
+                buf.append(s.charAt(i));
+            }
+        }
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * append hex digit
+     * 
+     */
+    public static void append(StringBuilder buf,byte b,int base)
+    {
+        int bi=0xff&b;
+        int c='0'+(bi/base)%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+        c='0'+bi%base;
+        if (c>'9')
+            c= 'a'+(c-'0'-10);
+        buf.append((char)c);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuffer buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void append2digits(StringBuilder buf,int i)
+    {
+        if (i<100)
+        {
+            buf.append((char)(i/10+'0'));
+            buf.append((char)(i%10+'0'));
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return a non null string.
+     * @param s String
+     * @return The string passed in or empty string if it is null. 
+     */
+    public static String nonNull(String s)
+    {
+        if (s==null)
+            return "";
+        return s;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean equals(String s,char[] buf, int offset, int length)
+    {
+        if (s.length()!=length)
+            return false;
+        for (int i=0;i<length;i++)
+            if (buf[offset+i]!=s.charAt(i))
+                return false;
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toUTF8String(byte[] b,int offset,int length)
+    {
+        try
+        {
+            return new String(b,offset,length,__UTF8);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] b,int offset,int length,String charset)
+    {
+        try
+        {
+            return new String(b,offset,length,charset);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static boolean isUTF8(String charset)
+    {
+        return __UTF8.equalsIgnoreCase(charset)||__UTF8Alt.equalsIgnoreCase(charset);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public static String printable(String name)
+    {
+        if (name==null)
+            return null;
+        StringBuilder buf = new StringBuilder(name.length());
+        for (int i=0;i<name.length();i++)
+        {
+            char c=name.charAt(i);
+            if (!Character.isISOControl(c))
+                buf.append(c);
+        }
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String printable(byte[] b)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=0;i<b.length;i++)
+        {
+            char c=(char)b[i];
+            if (Character.isWhitespace(c)|| c>' ' && c<0x7f)
+                buf.append(c);
+            else 
+            {
+                buf.append("0x");
+                TypeUtil.toHex(b[i],buf);
+            }
+        }
+        return buf.toString();
+    }
+    
+    public static byte[] getBytes(String s)
+    {
+        try
+        {
+            return s.getBytes(__ISO_8859_1);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return s.getBytes();
+        }
+    }
+    
+    public static byte[] getBytes(String s,String charset)
+    {
+        try
+        {
+            return s.getBytes(charset);
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+            return s.getBytes();
+        }
+    }
+    
+    
+    
+    /**
+     * Converts a binary SID to a string SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static String sidBytesToString(byte[] sidBytes)
+    {
+        StringBuilder sidString = new StringBuilder();
+        
+        // Identify this as a SID
+        sidString.append("S-");
+        
+        // Add SID revision level (expect 1 but may change someday)
+        sidString.append(Byte.toString(sidBytes[0])).append('-');
+        
+        StringBuilder tmpBuilder = new StringBuilder();
+        
+        // crunch the six bytes of issuing authority value
+        for (int i = 2; i <= 7; ++i)
+        {
+            tmpBuilder.append(Integer.toHexString(sidBytes[i] & 0xFF));
+        }
+        
+        sidString.append(Long.parseLong(tmpBuilder.toString(), 16)); // '-' is in the subauth loop
+   
+        // the number of subAuthorities we need to attach
+        int subAuthorityCount = sidBytes[1];
+
+        // attach each of the subAuthorities
+        for (int i = 0; i < subAuthorityCount; ++i)
+        {
+            int offset = i * 4;
+            tmpBuilder.setLength(0);
+            // these need to be zero padded hex and little endian
+            tmpBuilder.append(String.format("%02X%02X%02X%02X", 
+                    (sidBytes[11 + offset] & 0xFF),
+                    (sidBytes[10 + offset] & 0xFF),
+                    (sidBytes[9 + offset] & 0xFF),
+                    (sidBytes[8 + offset] & 0xFF)));  
+            sidString.append('-').append(Long.parseLong(tmpBuilder.toString(), 16));
+        }
+        
+        return sidString.toString();
+    }
+    
+    /**
+     * Converts a string SID to a binary SID
+     * 
+     * http://en.wikipedia.org/wiki/Security_Identifier
+     * 
+     * S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn
+     */
+    public static byte[] sidStringToBytes( String sidString )
+    {
+        String[] sidTokens = sidString.split("-");
+        
+        int subAuthorityCount = sidTokens.length - 3; // S-Rev-IdAuth-
+        
+        int byteCount = 0;
+        byte[] sidBytes = new byte[1 + 1 + 6 + (4 * subAuthorityCount)];
+        
+        // the revision byte
+        sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]);
+
+        // the # of sub authorities byte
+        sidBytes[byteCount++] = (byte)subAuthorityCount;
+
+        // the certAuthority
+        String hexStr = Long.toHexString(Long.parseLong(sidTokens[2]));
+        
+        while( hexStr.length() < 12) // pad to 12 characters
+        {
+            hexStr = "0" + hexStr;
+        }
+
+        // place the certAuthority 6 bytes
+        for ( int i = 0 ; i < hexStr.length(); i = i + 2)
+        {
+            sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(i, i + 2),16);
+        }
+                
+        
+        for ( int i = 3; i < sidTokens.length ; ++i)
+        {
+            hexStr = Long.toHexString(Long.parseLong(sidTokens[i]));
+            
+            while( hexStr.length() < 8) // pad to 8 characters
+            {
+                hexStr = "0" + hexStr;
+            }     
+            
+            // place the inverted sub authorities, 4 bytes each
+            for ( int j = hexStr.length(); j > 0; j = j - 2)
+            {          
+                sidBytes[byteCount++] = (byte)Integer.parseInt(hexStr.substring(j-2, j),16);
+            }
+        }
+      
+        return sidBytes;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/TypeUtil.java b/src/java/org/eclipse/jetty/util/TypeUtil.java
new file mode 100644
index 0000000..0464f0a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/TypeUtil.java
@@ -0,0 +1,593 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * TYPE Utilities.
+ * Provides various static utiltiy methods for manipulating types and their
+ * string representations.
+ *
+ * @since Jetty 4.1
+ */
+public class TypeUtil
+{
+    private static final Logger LOG = Log.getLogger(TypeUtil.class);
+    public static int CR = '\015';
+    public static int LF = '\012';
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<String, Class<?>> name2Class=new HashMap<String, Class<?>>();
+    static
+    {
+        name2Class.put("boolean",java.lang.Boolean.TYPE);
+        name2Class.put("byte",java.lang.Byte.TYPE);
+        name2Class.put("char",java.lang.Character.TYPE);
+        name2Class.put("double",java.lang.Double.TYPE);
+        name2Class.put("float",java.lang.Float.TYPE);
+        name2Class.put("int",java.lang.Integer.TYPE);
+        name2Class.put("long",java.lang.Long.TYPE);
+        name2Class.put("short",java.lang.Short.TYPE);
+        name2Class.put("void",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean.TYPE",java.lang.Boolean.TYPE);
+        name2Class.put("java.lang.Byte.TYPE",java.lang.Byte.TYPE);
+        name2Class.put("java.lang.Character.TYPE",java.lang.Character.TYPE);
+        name2Class.put("java.lang.Double.TYPE",java.lang.Double.TYPE);
+        name2Class.put("java.lang.Float.TYPE",java.lang.Float.TYPE);
+        name2Class.put("java.lang.Integer.TYPE",java.lang.Integer.TYPE);
+        name2Class.put("java.lang.Long.TYPE",java.lang.Long.TYPE);
+        name2Class.put("java.lang.Short.TYPE",java.lang.Short.TYPE);
+        name2Class.put("java.lang.Void.TYPE",java.lang.Void.TYPE);
+
+        name2Class.put("java.lang.Boolean",java.lang.Boolean.class);
+        name2Class.put("java.lang.Byte",java.lang.Byte.class);
+        name2Class.put("java.lang.Character",java.lang.Character.class);
+        name2Class.put("java.lang.Double",java.lang.Double.class);
+        name2Class.put("java.lang.Float",java.lang.Float.class);
+        name2Class.put("java.lang.Integer",java.lang.Integer.class);
+        name2Class.put("java.lang.Long",java.lang.Long.class);
+        name2Class.put("java.lang.Short",java.lang.Short.class);
+
+        name2Class.put("Boolean",java.lang.Boolean.class);
+        name2Class.put("Byte",java.lang.Byte.class);
+        name2Class.put("Character",java.lang.Character.class);
+        name2Class.put("Double",java.lang.Double.class);
+        name2Class.put("Float",java.lang.Float.class);
+        name2Class.put("Integer",java.lang.Integer.class);
+        name2Class.put("Long",java.lang.Long.class);
+        name2Class.put("Short",java.lang.Short.class);
+
+        name2Class.put(null,java.lang.Void.TYPE);
+        name2Class.put("string",java.lang.String.class);
+        name2Class.put("String",java.lang.String.class);
+        name2Class.put("java.lang.String",java.lang.String.class);
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, String> class2Name=new HashMap<Class<?>, String>();
+    static
+    {
+        class2Name.put(java.lang.Boolean.TYPE,"boolean");
+        class2Name.put(java.lang.Byte.TYPE,"byte");
+        class2Name.put(java.lang.Character.TYPE,"char");
+        class2Name.put(java.lang.Double.TYPE,"double");
+        class2Name.put(java.lang.Float.TYPE,"float");
+        class2Name.put(java.lang.Integer.TYPE,"int");
+        class2Name.put(java.lang.Long.TYPE,"long");
+        class2Name.put(java.lang.Short.TYPE,"short");
+        class2Name.put(java.lang.Void.TYPE,"void");
+
+        class2Name.put(java.lang.Boolean.class,"java.lang.Boolean");
+        class2Name.put(java.lang.Byte.class,"java.lang.Byte");
+        class2Name.put(java.lang.Character.class,"java.lang.Character");
+        class2Name.put(java.lang.Double.class,"java.lang.Double");
+        class2Name.put(java.lang.Float.class,"java.lang.Float");
+        class2Name.put(java.lang.Integer.class,"java.lang.Integer");
+        class2Name.put(java.lang.Long.class,"java.lang.Long");
+        class2Name.put(java.lang.Short.class,"java.lang.Short");
+
+        class2Name.put(null,"void");
+        class2Name.put(java.lang.String.class,"java.lang.String");
+    }
+
+    /* ------------------------------------------------------------ */
+    private static final HashMap<Class<?>, Method> class2Value=new HashMap<Class<?>, Method>();
+    static
+    {
+        try
+        {
+            Class<?>[] s ={java.lang.String.class};
+
+            class2Value.put(java.lang.Boolean.TYPE,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.TYPE,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.TYPE,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.TYPE,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.TYPE,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.TYPE,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.TYPE,
+                           java.lang.Short.class.getMethod("valueOf",s));
+
+            class2Value.put(java.lang.Boolean.class,
+                           java.lang.Boolean.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Byte.class,
+                           java.lang.Byte.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Double.class,
+                           java.lang.Double.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Float.class,
+                           java.lang.Float.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Integer.class,
+                           java.lang.Integer.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Long.class,
+                           java.lang.Long.class.getMethod("valueOf",s));
+            class2Value.put(java.lang.Short.class,
+                           java.lang.Short.class.getMethod("valueOf",s));
+        }
+        catch(Exception e)
+        {
+            throw new Error(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Array to List.
+     * <p>
+     * Works like {@link Arrays#asList(Object...)}, but handles null arrays.
+     * @return a list backed by the array.
+     */
+    public static <T> List<T> asList(T[] a) 
+    {
+        if (a==null)
+            return Collections.emptyList();
+        return Arrays.asList(a);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Class from a canonical name for a type.
+     * @param name A class or type name.
+     * @return A class , which may be a primitive TYPE field..
+     */
+    public static Class<?> fromName(String name)
+    {
+        return name2Class.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Canonical name for a type.
+     * @param type A class , which may be a primitive TYPE field.
+     * @return Canonical name.
+     */
+    public static String toName(Class<?> type)
+    {
+        return class2Name.get(type);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type The class of the instance, which may be a primitive TYPE field.
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(Class<?> type, String value)
+    {
+        try
+        {
+            if (type.equals(java.lang.String.class))
+                return value;
+
+            Method m = class2Value.get(type);
+            if (m!=null)
+                return m.invoke(null, value);
+
+            if (type.equals(java.lang.Character.TYPE) ||
+                type.equals(java.lang.Character.class))
+                return new Character(value.charAt(0));
+
+            Constructor<?> c = type.getConstructor(java.lang.String.class);
+            return c.newInstance(value);
+        }
+        catch(NoSuchMethodException e)
+        {
+            // LogSupport.ignore(log,e);
+        }
+        catch(IllegalAccessException e)
+        {
+            // LogSupport.ignore(log,e);
+        }
+        catch(InstantiationException e)
+        {
+            // LogSupport.ignore(log,e);
+        }
+        catch(InvocationTargetException e)
+        {
+            if (e.getTargetException() instanceof Error)
+                throw (Error)(e.getTargetException());
+            // LogSupport.ignore(log,e);
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert String value to instance.
+     * @param type classname or type (eg int)
+     * @param value The value as a string.
+     * @return The value as an Object.
+     */
+    public static Object valueOf(String type, String value)
+    {
+        return valueOf(fromName(type),value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a substring.
+     * Negative numbers are not handled.
+     * @param s String
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the string cannot be parsed
+     */
+    public static int parseInt(String s, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=s.length()-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=s.charAt(offset+i);
+
+            int digit=convertHexDigit((int)c);
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(s.substring(offset,offset+length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse an int from a byte array of ascii characters.
+     * Negative numbers are not handled.
+     * @param b byte array
+     * @param offset Offset within string
+     * @param length Length of integer or -1 for remainder of string
+     * @param base base of the integer
+     * @return the parsed integer
+     * @throws NumberFormatException if the array cannot be parsed into an integer
+     */
+    public static int parseInt(byte[] b, int offset, int length, int base)
+        throws NumberFormatException
+    {
+        int value=0;
+
+        if (length<0)
+            length=b.length-offset;
+
+        for (int i=0;i<length;i++)
+        {
+            char c=(char)(0xff&b[offset+i]);
+
+            int digit=c-'0';
+            if (digit<0 || digit>=base || digit>=10)
+            {
+                digit=10+c-'A';
+                if (digit<10 || digit>=base)
+                    digit=10+c-'a';
+            }
+            if (digit<0 || digit>=base)
+                throw new NumberFormatException(new String(b,offset,length));
+            value=value*base+digit;
+        }
+        return value;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] parseBytes(String s, int base)
+    {
+        byte[] bytes=new byte[s.length()/2];
+        for (int i=0;i<s.length();i+=2)
+            bytes[i/2]=(byte)TypeUtil.parseInt(s,i,2,base);
+        return bytes;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toString(byte[] bytes, int base)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (byte b : bytes)
+        {
+            int bi=0xff&b;
+            int c='0'+(bi/base)%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%base;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static byte convertHexDigit( byte c )
+    {
+        byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (b<0 || b>15)
+            throw new IllegalArgumentException("!hex "+c);
+        return b;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c An ASCII encoded character 0-9 a-f A-F
+     * @return The byte value of the character 0-16.
+     */
+    public static int convertHexDigit( int c )
+    {
+        int d= ((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
+        if (d<0 || d>15)
+            throw new NumberFormatException("!hex "+c);
+        return d;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(byte b,Appendable buf)
+    {
+        try
+        {
+            int d=0xf&((0xF0&b)>>4);
+            buf.append((char)((d>9?('A'-10):'0')+d));
+            d=0xf&b;
+            buf.append((char)((d>9?('A'-10):'0')+d));
+        }
+        catch(IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void toHex(int value,Appendable buf) throws IOException
+    {
+        int d=0xf&((0xF0000000&value)>>28);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0F000000&value)>>24);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00F00000&value)>>20);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000F0000&value)>>16);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x0000F000&value)>>12);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x00000F00&value)>>8);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&((0x000000F0&value)>>4);
+        buf.append((char)((d>9?('A'-10):'0')+d));
+        d=0xf&value;
+        buf.append((char)((d>9?('A'-10):'0')+d));
+    
+        Integer.toString(0,36);
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    public static void toHex(long value,Appendable buf) throws IOException
+    {
+        toHex((int)(value>>32),buf);
+        toHex((int)value,buf);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte b)
+    {
+        return toHexString(new byte[]{b}, 0, 1);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b)
+    {
+        return toHexString(b, 0, b.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String toHexString(byte[] b,int offset,int length)
+    {
+        StringBuilder buf = new StringBuilder();
+        for (int i=offset;i<offset+length;i++)
+        {
+            int bi=0xff&b[i];
+            int c='0'+(bi/16)%16;
+            if (c>'9')
+                c= 'A'+(c-'0'-10);
+            buf.append((char)c);
+            c='0'+bi%16;
+            if (c>'9')
+                c= 'a'+(c-'0'-10);
+            buf.append((char)c);
+        }
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static byte[] fromHexString(String s)
+    {
+        if (s.length()%2!=0)
+            throw new IllegalArgumentException(s);
+        byte[] array = new byte[s.length()/2];
+        for (int i=0;i<array.length;i++)
+        {
+            int b = Integer.parseInt(s.substring(i*2,i*2+2),16);
+            array[i]=(byte)(0xff&b);
+        }
+        return array;
+    }
+
+
+    public static void dump(Class<?> c)
+    {
+        System.err.println("Dump: "+c);
+        dump(c.getClassLoader());
+    }
+
+    public static void dump(ClassLoader cl)
+    {
+        System.err.println("Dump Loaders:");
+        while(cl!=null)
+        {
+            System.err.println("  loader "+cl);
+            cl = cl.getParent();
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @deprecated
+     */
+    public static byte[] readLine(InputStream in) throws IOException
+    {
+        byte[] buf = new byte[256];
+
+        int i=0;
+        int loops=0;
+        int ch=0;
+
+        while (true)
+        {
+            ch=in.read();
+            if (ch<0)
+                break;
+            loops++;
+
+            // skip a leading LF's
+            if (loops==1 && ch==LF)
+                continue;
+
+            if (ch==CR || ch==LF)
+                break;
+
+            if (i>=buf.length)
+            {
+                byte[] old_buf=buf;
+                buf=new byte[old_buf.length+256];
+                System.arraycopy(old_buf, 0, buf, 0, old_buf.length);
+            }
+            buf[i++]=(byte)ch;
+        }
+
+        if (ch==-1 && i==0)
+            return null;
+
+        // skip a trailing LF if it exists
+        if (ch==CR && in.available()>=1 && in.markSupported())
+        {
+            in.mark(1);
+            ch=in.read();
+            if (ch!=LF)
+                in.reset();
+        }
+
+        byte[] old_buf=buf;
+        buf=new byte[i];
+        System.arraycopy(old_buf, 0, buf, 0, i);
+
+        return buf;
+    }
+
+    public static URL jarFor(String className)
+    {
+        try
+        {
+            className=className.replace('.','/')+".class";
+            // hack to discover jstl libraries
+            URL url = Loader.getResource(null,className,false);
+            String s=url.toString();
+            if (s.startsWith("jar:file:"))
+                return new URL(s.substring(4,s.indexOf("!/")));
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+        return null;
+    }
+    
+    public static Object call(Class<?> oClass, String method, Object obj, Object[] arg) 
+       throws InvocationTargetException, NoSuchMethodException
+    {
+        // Lets just try all methods for now
+        Method[] methods = oClass.getMethods();
+        for (int c = 0; methods != null && c < methods.length; c++)
+        {
+            if (!methods[c].getName().equals(method))
+                continue;
+            if (methods[c].getParameterTypes().length != arg.length)
+                continue;
+            if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null))
+                continue;
+            if ((obj == null) && methods[c].getDeclaringClass() != oClass)
+                continue;
+
+            try
+            {
+                return methods[c].invoke(obj,arg);
+            }
+            catch (IllegalAccessException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+
+        throw new NoSuchMethodException(method);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/URIUtil.java b/src/java/org/eclipse/jetty/util/URIUtil.java
new file mode 100644
index 0000000..490f5db
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/URIUtil.java
@@ -0,0 +1,692 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+
+import org.eclipse.jetty.util.log.Log;
+
+
+
+/* ------------------------------------------------------------ */
+/** URI Holder.
+ * This class assists with the decoding and encoding or HTTP URI's.
+ * It differs from the java.net.URL class as it does not provide
+ * communications ability, but it does assist with query string
+ * formatting.
+ * <P>UTF-8 encoding is used by default for % encoded characters. This
+ * may be overridden with the org.eclipse.jetty.util.URI.charset system property.
+ * @see UrlEncoded
+ * 
+ */
+public class URIUtil
+    implements Cloneable
+{
+    public static final String SLASH="/";
+    public static final String HTTP="http";
+    public static final String HTTP_COLON="http:";
+    public static final String HTTPS="https";
+    public static final String HTTPS_COLON="https:";
+
+    // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars
+    public static final String __CHARSET=System.getProperty("org.eclipse.jetty.util.URI.charset",StringUtil.__UTF8);
+    
+    private URIUtil()
+    {}
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * This is the same encoding offered by URLEncoder, except that
+     * the '/' character is not encoded.
+     * @param path The path the encode
+     * @return The encoded path
+     */
+    public static String encodePath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+        
+        StringBuilder buf = encodePath(null,path);
+        return buf==null?path:buf.toString();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodePath(StringBuilder buf, String path)
+    {
+        byte[] bytes=null;
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                switch(c)
+                {
+                    case '%':
+                    case '?':
+                    case ';':
+                    case '#':
+                    case '\'':
+                    case '"':
+                    case '<':
+                    case '>':
+                    case ' ':
+                        buf=new StringBuilder(path.length()*2);
+                        break loop;
+                    default:
+                        if (c>127)
+                        {
+                            try
+                            {
+                                bytes=path.getBytes(URIUtil.__CHARSET);
+                            }
+                            catch (UnsupportedEncodingException e)
+                            {
+                                throw new IllegalStateException(e);
+                            }
+                            buf=new StringBuilder(path.length()*2);
+                            break loop;
+                        }
+                       
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            if (bytes!=null)
+            {
+                for (int i=0;i<bytes.length;i++)
+                {
+                    byte c=bytes[i];       
+                    switch(c)
+                    {
+                      case '%':
+                          buf.append("%25");
+                          continue;
+                      case '?':
+                          buf.append("%3F");
+                          continue;
+                      case ';':
+                          buf.append("%3B");
+                          continue;
+                      case '#':
+                          buf.append("%23");
+                          continue;
+                      case '"':
+                          buf.append("%22");
+                          continue;
+                      case '\'':
+                          buf.append("%27");
+                          continue;
+                      case '<':
+                          buf.append("%3C");
+                          continue;
+                      case '>':
+                          buf.append("%3E");
+                          continue;
+                      case ' ':
+                          buf.append("%20");
+                          continue;
+                      default:
+                          if (c<0)
+                          {
+                              buf.append('%');
+                              TypeUtil.toHex(c,buf);
+                          }
+                          else
+                              buf.append((char)c);
+                          continue;
+                    }
+                }
+                
+            }
+            else
+            {
+                for (int i=0;i<path.length();i++)
+                {
+                    char c=path.charAt(i);       
+                    switch(c)
+                    {
+                        case '%':
+                            buf.append("%25");
+                            continue;
+                        case '?':
+                            buf.append("%3F");
+                            continue;
+                        case ';':
+                            buf.append("%3B");
+                            continue;
+                        case '#':
+                            buf.append("%23");
+                            continue;
+                        case '"':
+                            buf.append("%22");
+                            continue;
+                        case '\'':
+                            buf.append("%27");
+                            continue;
+                        case '<':
+                            buf.append("%3C");
+                            continue;
+                        case '>':
+                            buf.append("%3E");
+                            continue;
+                        case ' ':
+                            buf.append("%20");
+                            continue;
+                        default:
+                            buf.append(c);
+                            continue;
+                    }
+                }
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Encode a URI path.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into (or null)
+     * @param encode String of characters to encode. % is always encoded.
+     * @return The StringBuilder or null if no substitutions required.
+     */
+    public static StringBuilder encodeString(StringBuilder buf,
+                                             String path,
+                                             String encode)
+    {
+        if (buf==null)
+        {
+        loop:
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {    
+                    buf=new StringBuilder(path.length()<<1);
+                    break loop;
+                }
+            }
+            if (buf==null)
+                return null;
+        }
+        
+        synchronized(buf)
+        {
+            for (int i=0;i<path.length();i++)
+            {
+                char c=path.charAt(i);
+                if (c=='%' || encode.indexOf(c)>=0)
+                {
+                    buf.append('%');
+                    StringUtil.append(buf,(byte)(0xff&c),16);
+                }
+                else
+                    buf.append(c);
+            }
+        }
+
+        return buf;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(String path)
+    {
+        if (path==null)
+            return null;
+        // Array to hold all converted characters
+        char[] chars=null;
+        int n=0;
+        // Array to hold a sequence of %encodings
+        byte[] bytes=null;
+        int b=0;
+        
+        int len=path.length();
+        
+        for (int i=0;i<len;i++)
+        {
+            char c = path.charAt(i);
+
+            if (c=='%' && (i+2)<len)
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    bytes=new byte[len];
+                    path.getChars(0,i,chars,0);
+                }
+                bytes[b++]=(byte)(0xff&TypeUtil.parseInt(path,i+1,2,16));
+                i+=2;
+                continue;
+            }
+            else if (c==';')
+            {
+                if (chars==null)
+                {
+                    chars=new char[len];
+                    path.getChars(0,i,chars,0);
+                    n=i;
+                }
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            // Do we have some bytes to convert?
+            if (b>0)
+            {
+                // convert series of bytes and add to chars
+                String s;
+                try
+                {
+                    s=new String(bytes,0,b,__CHARSET);
+                }
+                catch (UnsupportedEncodingException e)
+                {       
+                    s=new String(bytes,0,b);
+                }
+                s.getChars(0,s.length(),chars,n);
+                n+=s.length();
+                b=0;
+            }
+            
+            chars[n++]=c;
+        }
+
+        if (chars==null)
+            return path;
+
+        // if we have a remaining sequence of bytes
+        if (b>0)
+        {
+            // convert series of bytes and add to chars
+            String s;
+            try
+            {
+                s=new String(bytes,0,b,__CHARSET);
+            }
+            catch (UnsupportedEncodingException e)
+            {       
+                s=new String(bytes,0,b);
+            }
+            s.getChars(0,s.length(),chars,n);
+            n+=s.length();
+        }
+        
+        return new String(chars,0,n);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters.
+     * @param path The path the encode
+     * @param buf StringBuilder to encode path into
+     */
+    public static String decodePath(byte[] buf, int offset, int length)
+    {
+        byte[] bytes=null;
+        int n=0;
+        
+        for (int i=0;i<length;i++)
+        {
+            byte b = buf[i + offset];
+            
+            if (b=='%' && (i+2)<length)
+            {
+                b=(byte)(0xff&TypeUtil.parseInt(buf,i+offset+1,2,16));
+                i+=2;
+            }
+            else if (b==';')
+            {
+                length=i;
+                break;
+            }
+            else if (bytes==null)
+            {
+                n++;
+                continue;
+            }
+            
+            if (bytes==null)
+            {
+                bytes=new byte[length];
+                for (int j=0;j<n;j++)
+                    bytes[j]=buf[j + offset];
+            }
+            
+            bytes[n++]=b;
+        }
+
+        if (bytes==null)
+            return StringUtil.toString(buf,offset,length,__CHARSET);
+        return StringUtil.toString(bytes,0,n,__CHARSET);
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Add two URI path segments.
+     * Handles null and empty paths, path and query params (eg ?a=b or
+     * ;JSESSIONID=xxx) and avoids duplicate '/'
+     * @param p1 URI path segment (should be encoded)
+     * @param p2 URI path segment (should be encoded)
+     * @return Legally combined path segments.
+     */
+    public static String addPaths(String p1, String p2)
+    {
+        if (p1==null || p1.length()==0)
+        {
+            if (p1!=null && p2==null)
+                return p1;
+            return p2;
+        }
+        if (p2==null || p2.length()==0)
+            return p1;
+        
+        int split=p1.indexOf(';');
+        if (split<0)
+            split=p1.indexOf('?');
+        if (split==0)
+            return p2+p1;
+        if (split<0)
+            split=p1.length();
+
+        StringBuilder buf = new StringBuilder(p1.length()+p2.length()+2);
+        buf.append(p1);
+        
+        if (buf.charAt(split-1)=='/')
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+            {
+                buf.deleteCharAt(split-1);
+                buf.insert(split-1,p2);
+            }
+            else
+                buf.insert(split,p2);
+        }
+        else
+        {
+            if (p2.startsWith(URIUtil.SLASH))
+                buf.insert(split,p2);
+            else
+            {
+                buf.insert(split,'/');
+                buf.insert(split+1,p2);
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Return the parent Path.
+     * Treat a URI like a directory path and return the parent directory.
+     */
+    public static String parentPath(String p)
+    {
+        if (p==null || URIUtil.SLASH.equals(p))
+            return null;
+        int slash=p.lastIndexOf('/',p.length()-2);
+        if (slash>=0)
+            return p.substring(0,slash+1);
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a cananonical form.
+     * All instances of "." and ".." are factored out.  Null is returned
+     * if the path tries to .. above its root.
+     * @param path 
+     * @return path or null.
+     */
+    public static String canonicalPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int end=path.length();
+        int start = path.lastIndexOf('/', end);
+
+    search:
+        while (end>0)
+        {
+            switch(end-start)
+            {
+              case 2: // possible single dot
+                  if (path.charAt(start+1)!='.')
+                      break;
+                  break search;
+              case 3: // possible double dot
+                  if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.')
+                      break;
+                  break search;
+            }
+            
+            end=start;
+            start=path.lastIndexOf('/',end-1);
+        }
+
+        // If we have checked the entire string
+        if (start>=end)
+            return path;
+        
+        StringBuilder buf = new StringBuilder(path);
+        int delStart=-1;
+        int delEnd=-1;
+        int skip=0;
+        
+        while (end>0)
+        {
+            switch(end-start)
+            {       
+              case 2: // possible single dot
+                  if (buf.charAt(start+1)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   
+                          delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/')
+                      break;
+                  
+                  if(delEnd<0)
+                      delEnd=end;
+                  delStart=start;
+                  if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/')
+                  {
+                      delStart++;
+                      if (delEnd<buf.length() && buf.charAt(delEnd)=='/')
+                          delEnd++;
+                      break;
+                  }
+                  if (end==buf.length())
+                      delStart++;
+                  
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+                  
+              case 3: // possible double dot
+                  if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.')
+                  {
+                      if (skip>0 && --skip==0)
+                      {   delStart=start>=0?start:0;
+                          if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                              delStart++;
+                      }
+                      break;
+                  }
+                  
+                  delStart=start;
+                  if (delEnd<0)
+                      delEnd=end;
+
+                  skip++;
+                  end=start--;
+                  while (start>=0 && buf.charAt(start)!='/')
+                      start--;
+                  continue;
+
+              default:
+                  if (skip>0 && --skip==0)
+                  {
+                      delStart=start>=0?start:0;
+                      if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.')
+                          delStart++;
+                  }
+            }     
+            
+            // Do the delete
+            if (skip<=0 && delStart>=0 && delEnd>=delStart)
+            {  
+                buf.delete(delStart,delEnd);
+                delStart=delEnd=-1;
+                if (skip>0)
+                    delEnd=end;
+            }
+            
+            end=start--;
+            while (start>=0 && buf.charAt(start)!='/')
+                start--;
+        }      
+
+        // Too many ..
+        if (skip>0)
+            return null;
+        
+        // Do the delete
+        if (delEnd>=0)
+            buf.delete(delStart,delEnd);
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Convert a path to a compact form.
+     * All instances of "//" and "///" etc. are factored out to single "/" 
+     * @param path 
+     * @return path
+     */
+    public static String compactPath(String path)
+    {
+        if (path==null || path.length()==0)
+            return path;
+
+        int state=0;
+        int end=path.length();
+        int i=0;
+        
+        loop:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    return path;
+                case '/':
+                    state++;
+                    if (state==2)
+                        break loop;
+                    break;
+                default:
+                    state=0;
+            }
+            i++;
+        }
+        
+        if (state<2)
+            return path;
+        
+        StringBuffer buf = new StringBuffer(path.length());
+        buf.append(path,0,i);
+        
+        loop2:
+        while (i<end)
+        {
+            char c=path.charAt(i);
+            switch(c)
+            {
+                case '?':
+                    buf.append(path,i,end);
+                    break loop2;
+                case '/':
+                    if (state++==0)
+                        buf.append(c);
+                    break;
+                default:
+                    state=0;
+                    buf.append(c);
+            }
+            i++;
+        }
+        
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param uri URI
+     * @return True if the uri has a scheme
+     */
+    public static boolean hasScheme(String uri)
+    {
+        for (int i=0;i<uri.length();i++)
+        {
+            char c=uri.charAt(i);
+            if (c==':')
+                return true;
+            if (!(c>='a'&&c<='z' ||
+                  c>='A'&&c<='Z' ||
+                  (i>0 &&(c>='0'&&c<='9' ||
+                          c=='.' ||
+                          c=='+' ||
+                          c=='-'))
+                  ))
+                break;
+        }
+        return false;
+    }
+    
+}
+
+
+
diff --git a/src/java/org/eclipse/jetty/util/UrlEncoded.java b/src/java/org/eclipse/jetty/util/UrlEncoded.java
new file mode 100644
index 0000000..692fba1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -0,0 +1,1034 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import static org.eclipse.jetty.util.TypeUtil.convertHexDigit;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Handles coding of MIME  "x-www-form-urlencoded".
+ * <p>
+ * This class handles the encoding and decoding for either
+ * the query string of a URL or the _content of a POST HTTP request.
+ *
+ * <h4>Notes</h4>
+ * The UTF-8 charset is assumed, unless otherwise defined by either
+ * passing a parameter or setting the "org.eclipse.jetty.util.UrlEncoding.charset"
+ * System property.
+ * <p>
+ * The hashtable either contains String single values, vectors
+ * of String or arrays of Strings.
+ * <p>
+ * This class is only partially synchronised.  In particular, simple
+ * get operations are not protected from concurrent updates.
+ *
+ * @see java.net.URLEncoder
+ */
+public class UrlEncoded extends MultiMap implements Cloneable
+{
+    private static final Logger LOG = Log.getLogger(UrlEncoded.class);
+
+    public static final String ENCODING = System.getProperty("org.eclipse.jetty.util.UrlEncoding.charset",StringUtil.__UTF8);
+
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded(UrlEncoded url)
+    {
+        super(url);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded()
+    {
+        super(6);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded(String s)
+    {
+        super(6);
+        decode(s,ENCODING);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public UrlEncoded(String s, String charset)
+    {
+        super(6);
+        decode(s,charset);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public void decode(String query)
+    {
+        decodeTo(query,this,ENCODING,-1);
+    }
+    
+    /* ----------------------------------------------------------------- */
+    public void decode(String query,String charset)
+    {
+        decodeTo(query,this,charset,-1);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode()
+    {
+        return encode(ENCODING,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     */
+    public String encode(String charset)
+    {
+        return encode(charset,false);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public synchronized String encode(String charset, boolean equalsForNullValue)
+    {
+        return encode(this,charset,equalsForNullValue);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Encode Hashtable with % encoding.
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     * for parameters without a value. e.g. "blah?a=&b=&c=".
+     */
+    public static String encode(MultiMap map, String charset, boolean equalsForNullValue)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        StringBuilder result = new StringBuilder(128);
+
+        Iterator iter = map.entrySet().iterator();
+        while(iter.hasNext())
+        {
+            Map.Entry entry = (Map.Entry)iter.next();
+
+            String key = entry.getKey().toString();
+            Object list = entry.getValue();
+            int s=LazyList.size(list);
+
+            if (s==0)
+            {
+                result.append(encodeString(key,charset));
+                if(equalsForNullValue)
+                    result.append('=');
+            }
+            else
+            {
+                for (int i=0;i<s;i++)
+                {
+                    if (i>0)
+                        result.append('&');
+                    Object val=LazyList.get(list,i);
+                    result.append(encodeString(key,charset));
+
+                    if (val!=null)
+                    {
+                        String str=val.toString();
+                        if (str.length()>0)
+                        {
+                            result.append('=');
+                            result.append(encodeString(str,charset));
+                        }
+                        else if (equalsForNullValue)
+                            result.append('=');
+                    }
+                    else if (equalsForNullValue)
+                        result.append('=');
+                }
+            }
+            if (iter.hasNext())
+                result.append('&');
+        }
+        return result.toString();
+    }
+
+
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap map, String charset)
+    {
+        decodeTo(content,map,charset,-1);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param content the string containing the encoded parameters
+     */
+    public static void decodeTo(String content, MultiMap map, String charset, int maxKeys)
+    {
+        if (charset==null)
+            charset=ENCODING;
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            int mark=-1;
+            boolean encoded=false;
+            for (int i=0;i<content.length();i++)
+            {
+                char c = content.charAt(i);
+                switch (c)
+                {
+                  case '&':
+                      int l=i-mark-1;
+                      value = l==0?"":
+                          (encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1,i));
+                      mark=i;
+                      encoded=false;
+                      if (key != null)
+                      {
+                          map.add(key,value);
+                      }
+                      else if (value!=null&&value.length()>0)
+                      {
+                          map.add(value,"");
+                      }
+                      key = null;
+                      value=null;
+                      if (maxKeys>0 && map.size()>maxKeys)
+                          throw new IllegalStateException("Form too many keys");
+                      break;
+                  case '=':
+                      if (key!=null)
+                          break;
+                      key = encoded?decodeString(content,mark+1,i-mark-1,charset):content.substring(mark+1,i);
+                      mark=i;
+                      encoded=false;
+                      break;
+                  case '+':
+                      encoded=true;
+                      break;
+                  case '%':
+                      encoded=true;
+                      break;
+                }                
+            }
+            
+            if (key != null)
+            {
+                int l=content.length()-mark-1;
+                value = l==0?"":(encoded?decodeString(content,mark+1,l,charset):content.substring(mark+1));
+                map.add(key,value);
+            }
+            else if (mark<content.length())
+            {
+                key = encoded
+                    ?decodeString(content,mark+1,content.length()-mark-1,charset)
+                    :content.substring(mark+1);
+                if (key != null && key.length() > 0)
+                {
+                    map.add(key,"");
+                }
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param raw the byte[] containing the encoded parameters
+     * @param offset the offset within raw to decode from
+     * @param length the length of the section to decode
+     * @param map the {@link MultiMap} to populate
+     */
+    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map)
+    {
+        decodeUtf8To(raw,offset,length,map,new Utf8StringBuilder());
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param raw the byte[] containing the encoded parameters
+     * @param offset the offset within raw to decode from
+     * @param length the length of the section to decode
+     * @param map the {@link MultiMap} to populate
+     * @param buffer the buffer to decode into
+     */
+    public static void decodeUtf8To(byte[] raw,int offset, int length, MultiMap map,Utf8StringBuilder buffer)
+    {
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+
+            // TODO cache of parameter names ???
+            int end=offset+length;
+            for (int i=offset;i<end;i++)
+            {
+                byte b=raw[i];
+                try
+                {
+                    switch ((char)(0xff&b))
+                    {
+                        case '&':
+                            value = buffer.length()==0?"":buffer.toString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append(b);
+                                break;
+                            }
+                            key = buffer.toString();
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            if (i+2<end)
+                            {
+                                if ('u'==raw[i+1])
+                                {
+                                    i++;
+                                    if (i+4<end)
+                                        buffer.getStringBuilder().append(Character.toChars((convertHexDigit(raw[++i])<<12) +(convertHexDigit(raw[++i])<<8) + (convertHexDigit(raw[++i])<<4) +convertHexDigit(raw[++i])));
+                                    else
+                                    {
+                                        buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                        i=end;
+                                    }
+                                }
+                                else
+                                    buffer.append((byte)((convertHexDigit(raw[++i])<<4) + convertHexDigit(raw[++i])));
+                            }
+                            else
+                            {
+                                buffer.getStringBuilder().append(Utf8Appendable.REPLACEMENT);
+                                i=end;
+                            }
+                            break;
+                            
+                        default:
+                            buffer.append(b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+            }
+            
+            if (key != null)
+            {
+                value = buffer.length()==0?"":buffer.toReplacedString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toReplacedString(),"");
+            }
+        }
+    }
+
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            StringBuffer buffer = new StringBuffer();
+            String key = null;
+            String value = null;
+            
+            int b;
+
+            // TODO cache of parameter names ???
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                switch ((char) b)
+                {
+                    case '&':
+                        value = buffer.length()==0?"":buffer.toString();
+                        buffer.setLength(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                        
+                    case '=':
+                        if (key!=null)
+                        {
+                            buffer.append((char)b);
+                            break;
+                        }
+                        key = buffer.toString();
+                        buffer.setLength(0);
+                        break;
+                        
+                    case '+':
+                        buffer.append(' ');
+                        break;
+                        
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        buffer.append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                }
+                            }
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                buffer.append((char)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                        }
+                        break;
+                     
+                    default:
+                        buffer.append((char)b);
+                    break;
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.length()==0?"":buffer.toString();
+                buffer.setLength(0);
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in InputSteam to read
+     * @param map MultiMap to add parameters to
+     * @param maxLength maximum number of keys to read or -1 for no limit
+     */
+    public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys)
+    throws IOException
+    {
+        synchronized(map)
+        {
+            Utf8StringBuilder buffer = new Utf8StringBuilder();
+            String key = null;
+            String value = null;
+            
+            int b;
+            
+            // TODO cache of parameter names ???
+            int totalLength=0;
+            while ((b=in.read())>=0)
+            {
+                try
+                {
+                    switch ((char) b)
+                    {
+                        case '&':
+                            value = buffer.length()==0?"":buffer.toString();
+                            buffer.reset();
+                            if (key != null)
+                            {
+                                map.add(key,value);
+                            }
+                            else if (value!=null&&value.length()>0)
+                            {
+                                map.add(value,"");
+                            }
+                            key = null;
+                            value=null;
+                            if (maxKeys>0 && map.size()>maxKeys)
+                                throw new IllegalStateException("Form too many keys");
+                            break;
+
+                        case '=':
+                            if (key!=null)
+                            {
+                                buffer.append((byte)b);
+                                break;
+                            }
+                            key = buffer.toString();
+                            buffer.reset();
+                            break;
+
+                        case '+':
+                            buffer.append((byte)' ');
+                            break;
+
+                        case '%':
+                            int code0=in.read();
+                            if ('u'==code0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
+                                {
+                                    int code2=in.read();
+                                    if (code2>=0)
+                                    {
+                                        int code3=in.read();
+                                        if (code3>=0)
+                                            buffer.getStringBuilder().append(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3)));
+                                    }
+                                }
+                            }
+                            else if (code0>=0)
+                            {
+                                int code1=in.read();
+                                if (code1>=0)
+                                    buffer.append((byte)((convertHexDigit(code0)<<4)+convertHexDigit(code1)));
+                            }
+                            break;
+                          
+                        default:
+                            buffer.append((byte)b);
+                            break;
+                    }
+                }
+                catch(NotUtf8Exception e)
+                {
+                    LOG.warn(e.toString());
+                    LOG.debug(e);
+                }
+                if (maxLength>=0 && (++totalLength > maxLength))
+                    throw new IllegalStateException("Form too large");
+            }
+            
+            if (key != null)
+            {
+                value = buffer.length()==0?"":buffer.toString();
+                buffer.reset();
+                map.add(key,value);
+            }
+            else if (buffer.length()>0)
+            {
+                map.add(buffer.toString(), "");
+            }
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
+    {
+        InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
+        StringWriter buf = new StringWriter(8192);
+        IO.copy(input,buf,maxLength);
+        
+        decodeTo(buf.getBuffer().toString(),map,StringUtil.__UTF16,maxKeys);
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decoded parameters to Map.
+     * @param in the stream containing the encoded parameters
+     */
+    public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
+    throws IOException
+    {
+        //no charset present, use the configured default
+        if (charset==null) 
+        {
+           charset=ENCODING;
+        }
+            
+        if (StringUtil.__UTF8.equalsIgnoreCase(charset))
+        {
+            decodeUtf8To(in,map,maxLength,maxKeys);
+            return;
+        }
+        
+        if (StringUtil.__ISO_8859_1.equals(charset))
+        {
+            decode88591To(in,map,maxLength,maxKeys);
+            return;
+        }
+
+        if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
+        {
+            decodeUtf16To(in,map,maxLength,maxKeys);
+            return;
+        }
+        
+
+        synchronized(map)
+        {
+            String key = null;
+            String value = null;
+            
+            int c;
+            
+            int totalLength = 0;
+            ByteArrayOutputStream2 output = new ByteArrayOutputStream2();
+            
+            int size=0;
+            
+            while ((c=in.read())>0)
+            {
+                switch ((char) c)
+                {
+                    case '&':
+                        size=output.size();
+                        value = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        if (key != null)
+                        {
+                            map.add(key,value);
+                        }
+                        else if (value!=null&&value.length()>0)
+                        {
+                            map.add(value,"");
+                        }
+                        key = null;
+                        value=null;
+                        if (maxKeys>0 && map.size()>maxKeys)
+                            throw new IllegalStateException("Form too many keys");
+                        break;
+                    case '=':
+                        if (key!=null)
+                        {
+                            output.write(c);
+                            break;
+                        }
+                        size=output.size();
+                        key = size==0?"":output.toString(charset);
+                        output.setCount(0);
+                        break;
+                    case '+':
+                        output.write(' ');
+                        break;
+                    case '%':
+                        int code0=in.read();
+                        if ('u'==code0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                            {
+                                int code2=in.read();
+                                if (code2>=0)
+                                {
+                                    int code3=in.read();
+                                    if (code3>=0)
+                                        output.write(new String(Character.toChars((convertHexDigit(code0)<<12)+(convertHexDigit(code1)<<8)+(convertHexDigit(code2)<<4)+convertHexDigit(code3))).getBytes(charset));
+                                }
+                            }
+                            
+                        }
+                        else if (code0>=0)
+                        {
+                            int code1=in.read();
+                            if (code1>=0)
+                                output.write((convertHexDigit(code0)<<4)+convertHexDigit(code1));
+                        }
+                        break;
+                    default:
+                        output.write(c);
+                    break;
+                }
+                
+                totalLength++;
+                if (maxLength>=0 && totalLength > maxLength)
+                    throw new IllegalStateException("Form too large");
+            }
+
+            size=output.size();
+            if (key != null)
+            {
+                value = size==0?"":output.toString(charset);
+                output.setCount(0);
+                map.add(key,value);
+            }
+            else if (size>0)
+                map.add(output.toString(charset),"");
+        }
+    }
+    
+    /* -------------------------------------------------------------- */
+    /** Decode String with % encoding.
+     * This method makes the assumption that the majority of calls
+     * will need no decoding.
+     */
+    public static String decodeString(String encoded,int offset,int length,String charset)
+    {
+        if (charset==null || StringUtil.isUTF8(charset))
+        {
+            Utf8StringBuffer buffer=null;
+
+            for (int i=0;i<length;i++)
+            {
+                char c = encoded.charAt(offset+i);
+                if (c<0||c>0xff)
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i+1);
+                    }
+                    else
+                        buffer.getStringBuffer().append(c);
+                }
+                else if (c=='+')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    buffer.getStringBuffer().append(' ');
+                }
+                else if (c=='%')
+                {
+                    if (buffer==null)
+                    {
+                        buffer=new Utf8StringBuffer(length);
+                        buffer.getStringBuffer().append(encoded,offset,offset+i);
+                    }
+                    
+                    if ((i+2)<length)
+                    {
+                        try
+                        {
+                            if ('u'==encoded.charAt(offset+i+1))
+                            {
+                                if((i+5)<length)
+                                {
+                                    int o=offset+i+2;
+                                    i+=5;
+                                    String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                    buffer.getStringBuffer().append(unicode); 
+                                }
+                                else
+                                {
+                                    i=length;
+                                    buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                                }
+                            }
+                            else
+                            {
+                                int o=offset+i+1;
+                                i+=2;
+                                byte b=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                buffer.append(b);
+                            }
+                        }
+                        catch(NotUtf8Exception e)
+                        {
+                            LOG.warn(e.toString());
+                            LOG.debug(e);
+                        }
+                        catch(NumberFormatException nfe)
+                        {
+                            LOG.debug(nfe);
+                            buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT);  
+                        }
+                    }
+                    else
+                    {
+                        buffer.getStringBuffer().append(Utf8Appendable.REPLACEMENT); 
+                        i=length;
+                    }
+                }
+                else if (buffer!=null)
+                    buffer.getStringBuffer().append(c);
+            }
+
+            if (buffer==null)
+            {
+                if (offset==0 && encoded.length()==length)
+                    return encoded;
+                return encoded.substring(offset,offset+length);
+            }
+
+            return buffer.toReplacedString();
+        }
+        else
+        {
+            StringBuffer buffer=null;
+
+            try
+            {
+                for (int i=0;i<length;i++)
+                {
+                    char c = encoded.charAt(offset+i);
+                    if (c<0||c>0xff)
+                    {
+                        if (buffer==null)
+                        {
+                            buffer=new StringBuffer(length);
+                            buffer.append(encoded,offset,offset+i+1);
+                        }
+                        else
+                            buffer.append(c);
+                    }
+                    else if (c=='+')
+                    {
+                        if (buffer==null)
+                        {
+                            buffer=new StringBuffer(length);
+                            buffer.append(encoded,offset,offset+i);
+                        }
+                        
+                        buffer.append(' ');
+                    }
+                    else if (c=='%')
+                    {
+                        if (buffer==null)
+                        {
+                            buffer=new StringBuffer(length);
+                            buffer.append(encoded,offset,offset+i);
+                        }
+
+                        byte[] ba=new byte[length];
+                        int n=0;
+                        while(c>=0 && c<=0xff)
+                        {
+                            if (c=='%')
+                            {   
+                                if(i+2<length)
+                                {
+                                    try
+                                    {
+                                        if ('u'==encoded.charAt(offset+i+1))
+                                        {
+                                            if (i+6<length)
+                                            {
+                                                int o=offset+i+2;
+                                                i+=6;
+                                                String unicode = new String(Character.toChars(TypeUtil.parseInt(encoded,o,4,16)));
+                                                byte[] reencoded = unicode.getBytes(charset);
+                                                System.arraycopy(reencoded,0,ba,n,reencoded.length);
+                                                n+=reencoded.length;
+                                            }
+                                            else
+                                            {
+                                                ba[n++] = (byte)'?';
+                                                i=length;
+                                            }
+                                        }
+                                        else
+                                        {
+                                            int o=offset+i+1;
+                                            i+=3;
+                                            ba[n]=(byte)TypeUtil.parseInt(encoded,o,2,16);
+                                            n++;
+                                        }
+                                    }
+                                    catch(NumberFormatException nfe)
+                                    {   
+                                        LOG.ignore(nfe);
+                                        ba[n++] = (byte)'?';
+                                    }
+                                }
+                                else
+                                {
+                                    ba[n++] = (byte)'?';
+                                    i=length;
+                                }
+                            }
+                            else if (c=='+')
+                            {
+                                ba[n++]=(byte)' ';
+                                i++;
+                            }
+                            else
+                            {
+                                ba[n++]=(byte)c;
+                                i++;
+                            }
+                            
+                            if (i>=length)
+                                break;
+                            c = encoded.charAt(offset+i);
+                        }
+
+                        i--;
+                        buffer.append(new String(ba,0,n,charset));
+
+                    }
+                    else if (buffer!=null)
+                        buffer.append(c);
+                }
+
+                if (buffer==null)
+                {
+                    if (offset==0 && encoded.length()==length)
+                        return encoded;
+                    return encoded.substring(offset,offset+length);
+                }
+
+                return buffer.toString();
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string)
+    {
+        return encodeString(string,ENCODING);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Perform URL encoding.
+     * @param string 
+     * @return encoded string.
+     */
+    public static String encodeString(String string,String charset)
+    {
+        if (charset==null)
+            charset=ENCODING;
+        byte[] bytes=null;
+        try
+        {
+            bytes=string.getBytes(charset);
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            // LOG.warn(LogSupport.EXCEPTION,e);
+            bytes=string.getBytes();
+        }
+        
+        int len=bytes.length;
+        byte[] encoded= new byte[bytes.length*3];
+        int n=0;
+        boolean noEncode=true;
+        
+        for (int i=0;i<len;i++)
+        {
+            byte b = bytes[i];
+            
+            if (b==' ')
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'+';
+            }
+            else if (b>='a' && b<='z' ||
+                     b>='A' && b<='Z' ||
+                     b>='0' && b<='9')
+            {
+                encoded[n++]=b;
+            }
+            else
+            {
+                noEncode=false;
+                encoded[n++]=(byte)'%';
+                byte nibble= (byte) ((b&0xf0)>>4);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+                nibble= (byte) (b&0xf);
+                if (nibble>=10)
+                    encoded[n++]=(byte)('A'+nibble-10);
+                else
+                    encoded[n++]=(byte)('0'+nibble);
+            }
+        }
+
+        if (noEncode)
+            return string;
+        
+        try
+        {    
+            return new String(encoded,0,n,charset);
+        }
+        catch(UnsupportedEncodingException e)
+        {
+            // LOG.warn(LogSupport.EXCEPTION,e);
+            return new String(encoded,0,n);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** 
+     */
+    @Override
+    public Object clone()
+    {
+        return new UrlEncoded(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Utf8Appendable.java b/src/java/org/eclipse/jetty/util/Utf8Appendable.java
new file mode 100644
index 0000000..ffe403c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Utf8Appendable.java
@@ -0,0 +1,238 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Utf8 Appendable abstract base class
+ *
+ * This abstract class wraps a standard {@link java.lang.Appendable} and provides methods to append UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used. The UTF-8 code was inspired by
+ * http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ * License information for Bjoern Hoehrmann's code:
+ *
+ * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ **/
+public abstract class Utf8Appendable
+{
+    protected static final Logger LOG = Log.getLogger(Utf8Appendable.class);
+    public static final char REPLACEMENT = '\ufffd';
+    private static final int UTF8_ACCEPT = 0;
+    private static final int UTF8_REJECT = 12;
+
+    protected final Appendable _appendable;
+    protected int _state = UTF8_ACCEPT;
+
+    private static final byte[] BYTE_TABLE =
+    {
+        // The first part of the table maps bytes to character classes that
+        // to reduce the size of the transition table and create bitmasks.
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+         8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+        10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8
+    };
+
+    private static final byte[] TRANS_TABLE =
+    {
+        // The second part is a transition table that maps a combination
+        // of a state of the automaton and a character class to a state.
+         0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+        12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+        12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+        12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+        12,36,12,12,12,12,12,12,12,12,12,12
+    };
+
+    private int _codep;
+
+    public Utf8Appendable(Appendable appendable)
+    {
+        _appendable = appendable;
+    }
+
+    public abstract int length();
+
+    protected void reset()
+    {
+        _state = UTF8_ACCEPT;
+    }
+
+    public void append(byte b)
+    {
+        try
+        {
+            appendByte(b);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void append(byte[] b, int offset, int length)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+                appendByte(b[i]);
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean append(byte[] b, int offset, int length, int maxChars)
+    {
+        try
+        {
+            int end = offset + length;
+            for (int i = offset; i < end; i++)
+            {
+                if (length() > maxChars)
+                    return false;
+                appendByte(b[i]);
+            }
+            return true;
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected void appendByte(byte b) throws IOException
+    {
+
+        if (b > 0 && _state == UTF8_ACCEPT)
+        {
+            _appendable.append((char)(b & 0xFF));
+        }
+        else
+        {
+            int i = b & 0xFF;
+            int type = BYTE_TABLE[i];
+            _codep = _state == UTF8_ACCEPT ? (0xFF >> type) & i : (i & 0x3F) | (_codep << 6);
+            int next = TRANS_TABLE[_state + type];
+
+            switch(next)
+            {
+                case UTF8_ACCEPT:
+                    _state=next;
+                    if (_codep < Character.MIN_HIGH_SURROGATE)
+                    {
+                        _appendable.append((char)_codep);
+                    }
+                    else
+                    {
+                        for (char c : Character.toChars(_codep))
+                            _appendable.append(c);
+                    }
+                    break;
+                    
+                case UTF8_REJECT:
+                    String reason = "byte "+TypeUtil.toHexString(b)+" in state "+(_state/12);
+                    _codep=0;
+                    _state = UTF8_ACCEPT;
+                    _appendable.append(REPLACEMENT);
+                    throw new NotUtf8Exception(reason);
+                    
+                default:
+                    _state=next;
+                    
+            }
+        }
+    }
+
+    public boolean isUtf8SequenceComplete()
+    {
+        return _state == UTF8_ACCEPT;
+    }
+
+    public static class NotUtf8Exception extends IllegalArgumentException
+    {
+        public NotUtf8Exception(String reason)
+        {
+            super("Not valid UTF8! "+reason);
+        }
+    }
+
+    protected void checkState()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            throw new NotUtf8Exception("incomplete UTF8 sequence");
+        }
+    }
+    
+    public String toReplacedString()
+    {
+        if (!isUtf8SequenceComplete())
+        {
+            _codep=0;
+            _state = UTF8_ACCEPT;
+            try
+            {
+                _appendable.append(REPLACEMENT);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+            Throwable th= new NotUtf8Exception("incomplete UTF8 sequence");
+            LOG.warn(th.toString());
+            LOG.debug(th);
+        }
+        return _appendable.toString();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Utf8StringBuffer.java b/src/java/org/eclipse/jetty/util/Utf8StringBuffer.java
new file mode 100644
index 0000000..63fb1ac
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Utf8StringBuffer.java
@@ -0,0 +1,75 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+/* ------------------------------------------------------------ */
+/**
+ * UTF-8 StringBuffer.
+ *
+ * This class wraps a standard {@link java.lang.StringBuffer} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ */
+public class Utf8StringBuffer extends Utf8Appendable
+{
+    final StringBuffer _buffer;
+
+    public Utf8StringBuffer()
+    {
+        super(new StringBuffer());
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    public Utf8StringBuffer(int capacity)
+    {
+        super(new StringBuffer(capacity));
+        _buffer = (StringBuffer)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuffer getStringBuffer()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/Utf8StringBuilder.java b/src/java/org/eclipse/jetty/util/Utf8StringBuilder.java
new file mode 100644
index 0000000..28fa20b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/Utf8StringBuilder.java
@@ -0,0 +1,78 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+
+/* ------------------------------------------------------------ */
+/** UTF-8 StringBuilder.
+ *
+ * This class wraps a standard {@link java.lang.StringBuilder} and provides methods to append
+ * UTF-8 encoded bytes, that are converted into characters.
+ *
+ * This class is stateful and up to 4 calls to {@link #append(byte)} may be needed before
+ * state a character is appended to the string buffer.
+ *
+ * The UTF-8 decoding is done by this class and no additional buffers or Readers are used.
+ * The UTF-8 code was inspired by http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ *
+ */
+public class Utf8StringBuilder extends Utf8Appendable
+{
+    final StringBuilder _buffer;
+
+    public Utf8StringBuilder()
+    {
+        super(new StringBuilder());
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    public Utf8StringBuilder(int capacity)
+    {
+        super(new StringBuilder(capacity));
+        _buffer=(StringBuilder)_appendable;
+    }
+
+    @Override
+    public int length()
+    {
+        return _buffer.length();
+    }
+
+    @Override
+    public void reset()
+    {
+        super.reset();
+        _buffer.setLength(0);
+    }
+
+    public StringBuilder getStringBuilder()
+    {
+        checkState();
+        return _buffer;
+    }
+
+    @Override
+    public String toString()
+    {
+        checkState();
+        return _buffer.toString();
+    }
+
+
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSON.java b/src/java/org/eclipse/jetty/util/ajax/JSON.java
new file mode 100644
index 0000000..566179d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSON.java
@@ -0,0 +1,1640 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * JSON Parser and Generator.
+ * <p />
+ * This class provides some static methods to convert POJOs to and from JSON
+ * notation. The mapping from JSON to java is:
+ *
+ * <pre>
+ *   object ==> Map
+ *   array  ==> Object[]
+ *   number ==> Double or Long
+ *   string ==> String
+ *   null   ==> null
+ *   bool   ==> Boolean
+ * </pre>
+
+ * The java to JSON mapping is:
+ *
+ * <pre>
+ *   String --> string
+ *   Number --> number
+ *   Map    --> object
+ *   List   --> array
+ *   Array  --> array
+ *   null   --> null
+ *   Boolean--> boolean
+ *   Object --> string (dubious!)
+ * </pre>
+ *
+ * The interface {@link JSON.Convertible} may be implemented by classes that
+ * wish to externalize and initialize specific fields to and from JSON objects.
+ * Only directed acyclic graphs of objects are supported.
+ * <p />
+ * The interface {@link JSON.Generator} may be implemented by classes that know
+ * how to render themselves as JSON and the {@link #toString(Object)} method
+ * will use {@link JSON.Generator#addJSON(Appendable)} to generate the JSON.
+ * The class {@link JSON.Literal} may be used to hold pre-generated JSON object.
+ * <p />
+ * The interface {@link JSON.Convertor} may be implemented to provide static
+ * converters for objects that may be registered with
+ * {@link #registerConvertor(Class, Convertor)}.
+ * These converters are looked up by class, interface and super class by
+ * {@link #getConvertor(Class)}.
+ * <p />
+ * If a JSON object has a "class" field, then a java class for that name is
+ * loaded and the method {@link #convertTo(Class,Map)} is used to find a
+ * {@link JSON.Convertor} for that class.
+ * <p />
+ * If a JSON object has a "x-class" field then a direct lookup for a
+ * {@link JSON.Convertor} for that class name is done (without loading the class).
+ */
+public class JSON
+{
+    static final Logger LOG = Log.getLogger(JSON.class);
+    public final static JSON DEFAULT = new JSON();
+
+    private Map<String, Convertor> _convertors = new ConcurrentHashMap<String, Convertor>();
+    private int _stringBufferSize = 1024;
+
+    public JSON()
+    {
+    }
+
+    /**
+     * @return the initial stringBuffer size to use when creating JSON strings
+     *         (default 1024)
+     */
+    public int getStringBufferSize()
+    {
+        return _stringBufferSize;
+    }
+
+    /**
+     * @param stringBufferSize
+     *            the initial stringBuffer size to use when creating JSON
+     *            strings (default 1024)
+     */
+    public void setStringBufferSize(int stringBufferSize)
+    {
+        _stringBufferSize = stringBufferSize;
+    }
+
+    /**
+     * Register a {@link Convertor} for a class or interface.
+     *
+     * @param forClass
+     *            The class or interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public static void registerConvertor(Class forClass, Convertor convertor)
+    {
+        DEFAULT.addConvertor(forClass,convertor);
+    }
+
+    public static JSON getDefault()
+    {
+        return DEFAULT;
+    }
+
+    @Deprecated
+    public static void setDefault(JSON json)
+    {
+    }
+
+    public static String toString(Object object)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.append(buffer,object);
+        return buffer.toString();
+    }
+
+    public static String toString(Map object)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.appendMap(buffer,object);
+        return buffer.toString();
+    }
+
+    public static String toString(Object[] array)
+    {
+        StringBuilder buffer = new StringBuilder(DEFAULT.getStringBufferSize());
+        DEFAULT.appendArray(buffer,array);
+        return buffer.toString();
+    }
+
+    /**
+     * @param s
+     *            String containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(String s)
+    {
+        return DEFAULT.parse(new StringSource(s),false);
+    }
+
+    /**
+     * @param s
+     *            String containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(String s, boolean stripOuterComment)
+    {
+        return DEFAULT.parse(new StringSource(s),stripOuterComment);
+    }
+
+    /**
+     * @param in
+     *            Reader containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(Reader in) throws IOException
+    {
+        return DEFAULT.parse(new ReaderSource(in),false);
+    }
+
+    /**
+     * @param in
+     *            Reader containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    public static Object parse(Reader in, boolean stripOuterComment) throws IOException
+    {
+        return DEFAULT.parse(new ReaderSource(in),stripOuterComment);
+    }
+
+    /**
+     * @deprecated use {@link #parse(Reader)}
+     * @param in
+     *            Reader containing JSON object or array.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    @Deprecated
+    public static Object parse(InputStream in) throws IOException
+    {
+        return DEFAULT.parse(new StringSource(IO.toString(in)),false);
+    }
+
+    /**
+     * @deprecated use {@link #parse(Reader, boolean)}
+     * @param in
+     *            Stream containing JSON object or array.
+     * @param stripOuterComment
+     *            If true, an outer comment around the JSON is ignored.
+     * @return A Map, Object array or primitive array parsed from the JSON.
+     */
+    @Deprecated
+    public static Object parse(InputStream in, boolean stripOuterComment) throws IOException
+    {
+        return DEFAULT.parse(new StringSource(IO.toString(in)),stripOuterComment);
+    }
+
+    /**
+     * Convert Object to JSON
+     *
+     * @param object
+     *            The object to convert
+     * @return The JSON String
+     */
+    public String toJSON(Object object)
+    {
+        StringBuilder buffer = new StringBuilder(getStringBufferSize());
+        append(buffer,object);
+        return buffer.toString();
+    }
+
+    /**
+     * Convert JSON to Object
+     *
+     * @param json
+     *            The json to convert
+     * @return The object
+     */
+    public Object fromJSON(String json)
+    {
+        Source source = new StringSource(json);
+        return parse(source);
+    }
+
+    @Deprecated
+    public void append(StringBuffer buffer, Object object)
+    {
+        append((Appendable)buffer,object);
+    }
+
+    /**
+     * Append object as JSON to string buffer.
+     *
+     * @param buffer
+     *            the buffer to append to
+     * @param object
+     *            the object to append
+     */
+    public void append(Appendable buffer, Object object)
+    {
+        try
+        {
+            if (object == null)
+            {
+                buffer.append("null");
+            }
+            // Most likely first
+            else if (object instanceof Map)
+            {
+                appendMap(buffer,(Map)object);
+            }
+            else if (object instanceof String)
+            {
+                appendString(buffer,(String)object);
+            }
+            else if (object instanceof Number)
+            {
+                appendNumber(buffer,(Number)object);
+            }
+            else if (object instanceof Boolean)
+            {
+                appendBoolean(buffer,(Boolean)object);
+            }
+            else if (object.getClass().isArray())
+            {
+                appendArray(buffer,object);
+            }
+            else if (object instanceof Character)
+            {
+                appendString(buffer,object.toString());
+            }
+            else if (object instanceof Convertible)
+            {
+                appendJSON(buffer,(Convertible)object);
+            }
+            else if (object instanceof Generator)
+            {
+                appendJSON(buffer,(Generator)object);
+            }
+            else
+            {
+                // Check Convertor before Collection to support JSONCollectionConvertor
+                Convertor convertor = getConvertor(object.getClass());
+                if (convertor != null)
+                {
+                    appendJSON(buffer,convertor,object);
+                }
+                else if (object instanceof Collection)
+                {
+                    appendArray(buffer,(Collection)object);
+                }
+                else
+                {
+                    appendString(buffer,object.toString());
+                }
+            }
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendNull(StringBuffer buffer)
+    {
+        appendNull((Appendable)buffer);
+    }
+
+    public void appendNull(Appendable buffer)
+    {
+        try
+        {
+            buffer.append("null");
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendJSON(final StringBuffer buffer, final Convertor convertor, final Object object)
+    {
+        appendJSON((Appendable)buffer,convertor,object);
+    }
+
+    public void appendJSON(final Appendable buffer, final Convertor convertor, final Object object)
+    {
+        appendJSON(buffer,new Convertible()
+        {
+            public void fromJSON(Map object)
+            {
+            }
+
+            public void toJSON(Output out)
+            {
+                convertor.toJSON(object,out);
+            }
+        });
+    }
+
+    @Deprecated
+    public void appendJSON(final StringBuffer buffer, Convertible converter)
+    {
+        appendJSON((Appendable)buffer,converter);
+    }
+
+    public void appendJSON(final Appendable buffer, Convertible converter)
+    {
+        ConvertableOutput out=new ConvertableOutput(buffer);
+        converter.toJSON(out);
+        out.complete();
+    }
+
+    @Deprecated
+    public void appendJSON(StringBuffer buffer, Generator generator)
+    {
+        generator.addJSON(buffer);
+    }
+
+    public void appendJSON(Appendable buffer, Generator generator)
+    {
+        generator.addJSON(buffer);
+    }
+
+    @Deprecated
+    public void appendMap(StringBuffer buffer, Map<?,?> map)
+    {
+        appendMap((Appendable)buffer,map);
+    }
+
+    public void appendMap(Appendable buffer, Map<?,?> map)
+    {
+        try
+        {
+            if (map == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('{');
+            Iterator<?> iter = map.entrySet().iterator();
+            while (iter.hasNext())
+            {
+                Map.Entry<?,?> entry = (Map.Entry<?,?>)iter.next();
+                QuotedStringTokenizer.quote(buffer,entry.getKey().toString());
+                buffer.append(':');
+                append(buffer,entry.getValue());
+                if (iter.hasNext())
+                    buffer.append(',');
+            }
+
+            buffer.append('}');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendArray(StringBuffer buffer, Collection collection)
+    {
+        appendArray((Appendable)buffer,collection);
+    }
+
+    public void appendArray(Appendable buffer, Collection collection)
+    {
+        try
+        {
+            if (collection == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('[');
+            Iterator iter = collection.iterator();
+            boolean first = true;
+            while (iter.hasNext())
+            {
+                if (!first)
+                    buffer.append(',');
+
+                first = false;
+                append(buffer,iter.next());
+            }
+
+            buffer.append(']');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendArray(StringBuffer buffer, Object array)
+    {
+    appendArray((Appendable)buffer,array);
+    }
+
+    public void appendArray(Appendable buffer, Object array)
+    {
+        try
+        {
+            if (array == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+
+            buffer.append('[');
+            int length = Array.getLength(array);
+
+            for (int i = 0; i < length; i++)
+            {
+                if (i != 0)
+                    buffer.append(',');
+                append(buffer,Array.get(array,i));
+            }
+
+            buffer.append(']');
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendBoolean(StringBuffer buffer, Boolean b)
+    {
+        appendBoolean((Appendable)buffer,b);
+    }
+
+    public void appendBoolean(Appendable buffer, Boolean b)
+    {
+        try
+        {
+            if (b == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+            buffer.append(b?"true":"false");
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendNumber(StringBuffer buffer, Number number)
+    {
+        appendNumber((Appendable)buffer,number);
+    }
+
+    public void appendNumber(Appendable buffer, Number number)
+    {
+        try
+        {
+            if (number == null)
+            {
+                appendNull(buffer);
+                return;
+            }
+            buffer.append(String.valueOf(number));
+        }
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Deprecated
+    public void appendString(StringBuffer buffer, String string)
+    {
+        appendString((Appendable)buffer,string);
+    }
+
+    public void appendString(Appendable buffer, String string)
+    {
+        if (string == null)
+        {
+            appendNull(buffer);
+            return;
+        }
+
+        QuotedStringTokenizer.quote(buffer,string);
+    }
+
+    // Parsing utilities
+
+    protected String toString(char[] buffer, int offset, int length)
+    {
+        return new String(buffer,offset,length);
+    }
+
+    protected Map<String, Object> newMap()
+    {
+        return new HashMap<String, Object>();
+    }
+
+    protected Object[] newArray(int size)
+    {
+        return new Object[size];
+    }
+
+    protected JSON contextForArray()
+    {
+        return this;
+    }
+
+    protected JSON contextFor(String field)
+    {
+        return this;
+    }
+
+    protected Object convertTo(Class type, Map map)
+    {
+        if (type != null && Convertible.class.isAssignableFrom(type))
+        {
+            try
+            {
+                Convertible conv = (Convertible)type.newInstance();
+                conv.fromJSON(map);
+                return conv;
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        Convertor convertor = getConvertor(type);
+        if (convertor != null)
+        {
+            return convertor.fromJSON(map);
+        }
+        return map;
+    }
+
+    /**
+     * Register a {@link Convertor} for a class or interface.
+     *
+     * @param forClass
+     *            The class or interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public void addConvertor(Class forClass, Convertor convertor)
+    {
+        _convertors.put(forClass.getName(),convertor);
+    }
+
+    /**
+     * Lookup a convertor for a class.
+     * <p>
+     * If no match is found for the class, then the interfaces for the class are
+     * tried. If still no match is found, then the super class and it's
+     * interfaces are tried recursively.
+     *
+     * @param forClass
+     *            The class
+     * @return a {@link JSON.Convertor} or null if none were found.
+     */
+    protected Convertor getConvertor(Class forClass)
+    {
+        Class cls = forClass;
+        Convertor convertor = _convertors.get(cls.getName());
+        if (convertor == null && this != DEFAULT)
+            convertor = DEFAULT.getConvertor(cls);
+
+        while (convertor == null && cls != Object.class)
+        {
+            Class[] ifs = cls.getInterfaces();
+            int i = 0;
+            while (convertor == null && ifs != null && i < ifs.length)
+                convertor = _convertors.get(ifs[i++].getName());
+            if (convertor == null)
+            {
+                cls = cls.getSuperclass();
+                convertor = _convertors.get(cls.getName());
+            }
+        }
+        return convertor;
+    }
+
+    /**
+     * Register a {@link JSON.Convertor} for a named class or interface.
+     *
+     * @param name
+     *            name of a class or an interface that the convertor applies to
+     * @param convertor
+     *            the convertor
+     */
+    public void addConvertorFor(String name, Convertor convertor)
+    {
+        _convertors.put(name,convertor);
+    }
+
+    /**
+     * Lookup a convertor for a named class.
+     *
+     * @param name
+     *            name of the class
+     * @return a {@link JSON.Convertor} or null if none were found.
+     */
+    public Convertor getConvertorFor(String name)
+    {
+        Convertor convertor = _convertors.get(name);
+        if (convertor == null && this != DEFAULT)
+            convertor = DEFAULT.getConvertorFor(name);
+        return convertor;
+    }
+
+    public Object parse(Source source, boolean stripOuterComment)
+    {
+        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
+        if (!stripOuterComment)
+            return parse(source);
+
+        int strip_state = 1; // 0=no strip, 1=wait for /*, 2= wait for */
+
+        Object o = null;
+        while (source.hasNext())
+        {
+            char c = source.peek();
+
+            // handle // or /* comment
+            if (comment_state == 1)
+            {
+                switch (c)
+                {
+                    case '/':
+                        comment_state = -1;
+                        break;
+                    case '*':
+                        comment_state = 2;
+                        if (strip_state == 1)
+                        {
+                            comment_state = 0;
+                            strip_state = 2;
+                        }
+                }
+            }
+            // handle /* */ comment
+            else if (comment_state > 1)
+            {
+                switch (c)
+                {
+                    case '*':
+                        comment_state = 3;
+                        break;
+                    case '/':
+                        if (comment_state == 3)
+                        {
+                            comment_state = 0;
+                            if (strip_state == 2)
+                                return o;
+                        }
+                        else
+                            comment_state = 2;
+                        break;
+                    default:
+                        comment_state = 2;
+                }
+            }
+            // handle // comment
+            else if (comment_state < 0)
+            {
+                switch (c)
+                {
+                    case '\r':
+                    case '\n':
+                        comment_state = 0;
+                    default:
+                        break;
+                }
+            }
+            // handle unknown
+            else
+            {
+                if (!Character.isWhitespace(c))
+                {
+                    if (c == '/')
+                        comment_state = 1;
+                    else if (c == '*')
+                        comment_state = 3;
+                    else if (o == null)
+                    {
+                        o = parse(source);
+                        continue;
+                    }
+                }
+            }
+
+            source.next();
+        }
+
+        return o;
+    }
+
+    public Object parse(Source source)
+    {
+        int comment_state = 0; // 0=no comment, 1="/", 2="/*", 3="/* *" -1="//"
+
+        while (source.hasNext())
+        {
+            char c = source.peek();
+
+            // handle // or /* comment
+            if (comment_state == 1)
+            {
+                switch (c)
+                {
+                    case '/':
+                        comment_state = -1;
+                        break;
+                    case '*':
+                        comment_state = 2;
+                }
+            }
+            // handle /* */ comment
+            else if (comment_state > 1)
+            {
+                switch (c)
+                {
+                    case '*':
+                        comment_state = 3;
+                        break;
+                    case '/':
+                        if (comment_state == 3)
+                            comment_state = 0;
+                        else
+                            comment_state = 2;
+                        break;
+                    default:
+                        comment_state = 2;
+                }
+            }
+            // handle // comment
+            else if (comment_state < 0)
+            {
+                switch (c)
+                {
+                    case '\r':
+                    case '\n':
+                        comment_state = 0;
+                        break;
+                    default:
+                        break;
+                }
+            }
+            // handle unknown
+            else
+            {
+                switch (c)
+                {
+                    case '{':
+                        return parseObject(source);
+                    case '[':
+                        return parseArray(source);
+                    case '"':
+                        return parseString(source);
+                    case '-':
+                        return parseNumber(source);
+
+                    case 'n':
+                        complete("null",source);
+                        return null;
+                    case 't':
+                        complete("true",source);
+                        return Boolean.TRUE;
+                    case 'f':
+                        complete("false",source);
+                        return Boolean.FALSE;
+                    case 'u':
+                        complete("undefined",source);
+                        return null;
+                    case 'N':
+                        complete("NaN",source);
+                        return null;
+
+                    case '/':
+                        comment_state = 1;
+                        break;
+
+                    default:
+                        if (Character.isDigit(c))
+                            return parseNumber(source);
+                        else if (Character.isWhitespace(c))
+                            break;
+                        return handleUnknown(source,c);
+                }
+            }
+            source.next();
+        }
+
+        return null;
+    }
+
+    protected Object handleUnknown(Source source, char c)
+    {
+        throw new IllegalStateException("unknown char '" + c + "'(" + (int)c + ") in " + source);
+    }
+
+    protected Object parseObject(Source source)
+    {
+        if (source.next() != '{')
+            throw new IllegalStateException();
+        Map<String, Object> map = newMap();
+
+        char next = seekTo("\"}",source);
+
+        while (source.hasNext())
+        {
+            if (next == '}')
+            {
+                source.next();
+                break;
+            }
+
+            String name = parseString(source);
+            seekTo(':',source);
+            source.next();
+
+            Object value = contextFor(name).parse(source);
+            map.put(name,value);
+
+            seekTo(",}",source);
+            next = source.next();
+            if (next == '}')
+                break;
+            else
+                next = seekTo("\"}",source);
+        }
+
+        String xclassname = (String)map.get("x-class");
+        if (xclassname != null)
+        {
+            Convertor c = getConvertorFor(xclassname);
+            if (c != null)
+                return c.fromJSON(map);
+            LOG.warn("No Convertor for x-class '{}'", xclassname);
+        }
+
+        String classname = (String)map.get("class");
+        if (classname != null)
+        {
+            try
+            {
+                Class c = Loader.loadClass(JSON.class,classname);
+                return convertTo(c,map);
+            }
+            catch (ClassNotFoundException e)
+            {
+                LOG.warn("No Class for '{}'", classname);
+            }
+        }
+
+        return map;
+    }
+
+    protected Object parseArray(Source source)
+    {
+        if (source.next() != '[')
+            throw new IllegalStateException();
+
+        int size = 0;
+        ArrayList list = null;
+        Object item = null;
+        boolean coma = true;
+
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case ']':
+                    source.next();
+                    switch (size)
+                    {
+                        case 0:
+                            return newArray(0);
+                        case 1:
+                            Object array = newArray(1);
+                            Array.set(array,0,item);
+                            return array;
+                        default:
+                            return list.toArray(newArray(list.size()));
+                    }
+
+                case ',':
+                    if (coma)
+                        throw new IllegalStateException();
+                    coma = true;
+                    source.next();
+                    break;
+
+                default:
+                    if (Character.isWhitespace(c))
+                        source.next();
+                    else
+                    {
+                        coma = false;
+                        if (size++ == 0)
+                            item = contextForArray().parse(source);
+                        else if (list == null)
+                        {
+                            list = new ArrayList();
+                            list.add(item);
+                            item = contextForArray().parse(source);
+                            list.add(item);
+                            item = null;
+                        }
+                        else
+                        {
+                            item = contextForArray().parse(source);
+                            list.add(item);
+                            item = null;
+                        }
+                    }
+            }
+
+        }
+
+        throw new IllegalStateException("unexpected end of array");
+    }
+
+    protected String parseString(Source source)
+    {
+        if (source.next() != '"')
+            throw new IllegalStateException();
+
+        boolean escape = false;
+
+        StringBuilder b = null;
+        final char[] scratch = source.scratchBuffer();
+
+        if (scratch != null)
+        {
+            int i = 0;
+            while (source.hasNext())
+            {
+                if (i >= scratch.length)
+                {
+                    // we have filled the scratch buffer, so we must
+                    // use the StringBuffer for a large string
+                    b = new StringBuilder(scratch.length * 2);
+                    b.append(scratch,0,i);
+                    break;
+                }
+
+                char c = source.next();
+
+                if (escape)
+                {
+                    escape = false;
+                    switch (c)
+                    {
+                        case '"':
+                            scratch[i++] = '"';
+                            break;
+                        case '\\':
+                            scratch[i++] = '\\';
+                            break;
+                        case '/':
+                            scratch[i++] = '/';
+                            break;
+                        case 'b':
+                            scratch[i++] = '\b';
+                            break;
+                        case 'f':
+                            scratch[i++] = '\f';
+                            break;
+                        case 'n':
+                            scratch[i++] = '\n';
+                            break;
+                        case 'r':
+                            scratch[i++] = '\r';
+                            break;
+                        case 't':
+                            scratch[i++] = '\t';
+                            break;
+                        case 'u':
+                            char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
+                                    + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
+                            scratch[i++] = uc;
+                            break;
+                        default:
+                            scratch[i++] = c;
+                    }
+                }
+                else if (c == '\\')
+                {
+                    escape = true;
+                }
+                else if (c == '\"')
+                {
+                    // Return string that fits within scratch buffer
+                    return toString(scratch,0,i);
+                }
+                else
+                {
+                    scratch[i++] = c;
+                }
+            }
+
+            // Missing end quote, but return string anyway ?
+            if (b == null)
+                return toString(scratch,0,i);
+        }
+        else
+            b = new StringBuilder(getStringBufferSize());
+
+        // parse large string into string buffer
+        final StringBuilder builder=b;
+        while (source.hasNext())
+        {
+            char c = source.next();
+
+            if (escape)
+            {
+                escape = false;
+                switch (c)
+                {
+                    case '"':
+                        builder.append('"');
+                        break;
+                    case '\\':
+                        builder.append('\\');
+                        break;
+                    case '/':
+                        builder.append('/');
+                        break;
+                    case 'b':
+                        builder.append('\b');
+                        break;
+                    case 'f':
+                        builder.append('\f');
+                        break;
+                    case 'n':
+                        builder.append('\n');
+                        break;
+                    case 'r':
+                        builder.append('\r');
+                        break;
+                    case 't':
+                        builder.append('\t');
+                        break;
+                    case 'u':
+                        char uc = (char)((TypeUtil.convertHexDigit((byte)source.next()) << 12) + (TypeUtil.convertHexDigit((byte)source.next()) << 8)
+                                + (TypeUtil.convertHexDigit((byte)source.next()) << 4) + (TypeUtil.convertHexDigit((byte)source.next())));
+                        builder.append(uc);
+                        break;
+                    default:
+                        builder.append(c);
+                }
+            }
+            else if (c == '\\')
+            {
+                escape = true;
+            }
+            else if (c == '\"')
+            {
+                break;
+            }
+            else
+            {
+                builder.append(c);
+            }
+        }
+        return builder.toString();
+    }
+
+    public Number parseNumber(Source source)
+    {
+        boolean minus = false;
+        long number = 0;
+        StringBuilder buffer = null;
+
+        longLoop: while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                    number = number * 10 + (c - '0');
+                    source.next();
+                    break;
+
+                case '-':
+                case '+':
+                    if (number != 0)
+                        throw new IllegalStateException("bad number");
+                    minus = true;
+                    source.next();
+                    break;
+
+                case '.':
+                case 'e':
+                case 'E':
+                    buffer = new StringBuilder(16);
+                    if (minus)
+                        buffer.append('-');
+                    buffer.append(number);
+                    buffer.append(c);
+                    source.next();
+                    break longLoop;
+
+                default:
+                    break longLoop;
+            }
+        }
+
+        if (buffer == null)
+            return minus ? -1 * number : number;
+
+        doubleLoop: while (source.hasNext())
+        {
+            char c = source.peek();
+            switch (c)
+            {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                case '4':
+                case '5':
+                case '6':
+                case '7':
+                case '8':
+                case '9':
+                case '-':
+                case '.':
+                case '+':
+                case 'e':
+                case 'E':
+                    buffer.append(c);
+                    source.next();
+                    break;
+
+                default:
+                    break doubleLoop;
+            }
+        }
+        return new Double(buffer.toString());
+
+    }
+
+    protected void seekTo(char seek, Source source)
+    {
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            if (c == seek)
+                return;
+
+            if (!Character.isWhitespace(c))
+                throw new IllegalStateException("Unexpected '" + c + " while seeking '" + seek + "'");
+            source.next();
+        }
+
+        throw new IllegalStateException("Expected '" + seek + "'");
+    }
+
+    protected char seekTo(String seek, Source source)
+    {
+        while (source.hasNext())
+        {
+            char c = source.peek();
+            if (seek.indexOf(c) >= 0)
+            {
+                return c;
+            }
+
+            if (!Character.isWhitespace(c))
+                throw new IllegalStateException("Unexpected '" + c + "' while seeking one of '" + seek + "'");
+            source.next();
+        }
+
+        throw new IllegalStateException("Expected one of '" + seek + "'");
+    }
+
+    protected static void complete(String seek, Source source)
+    {
+        int i = 0;
+        while (source.hasNext() && i < seek.length())
+        {
+            char c = source.next();
+            if (c != seek.charAt(i++))
+                throw new IllegalStateException("Unexpected '" + c + " while seeking  \"" + seek + "\"");
+        }
+
+        if (i < seek.length())
+            throw new IllegalStateException("Expected \"" + seek + "\"");
+    }
+
+    private final class ConvertableOutput implements Output
+    {
+        private final Appendable _buffer;
+        char c = '{';
+
+        private ConvertableOutput(Appendable buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void complete()
+        {
+            try
+            {
+                if (c == '{')
+                    _buffer.append("{}");
+                else if (c != 0)
+                    _buffer.append("}");
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(Object obj)
+        {
+            if (c == 0)
+                throw new IllegalStateException();
+            append(_buffer,obj);
+            c = 0;
+        }
+
+        public void addClass(Class type)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                _buffer.append("\"class\":");
+                append(_buffer,type.getName());
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, Object value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                append(_buffer,value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, double value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendNumber(_buffer, value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, long value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendNumber(_buffer, value);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void add(String name, boolean value)
+        {
+            try
+            {
+                if (c == 0)
+                    throw new IllegalStateException();
+                _buffer.append(c);
+                QuotedStringTokenizer.quote(_buffer,name);
+                _buffer.append(':');
+                appendBoolean(_buffer,value?Boolean.TRUE:Boolean.FALSE);
+                c = ',';
+            }
+            catch (IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public interface Source
+    {
+        boolean hasNext();
+
+        char next();
+
+        char peek();
+
+        char[] scratchBuffer();
+    }
+
+    public static class StringSource implements Source
+    {
+        private final String string;
+        private int index;
+        private char[] scratch;
+
+        public StringSource(String s)
+        {
+            string = s;
+        }
+
+        public boolean hasNext()
+        {
+            if (index < string.length())
+                return true;
+            scratch = null;
+            return false;
+        }
+
+        public char next()
+        {
+            return string.charAt(index++);
+        }
+
+        public char peek()
+        {
+            return string.charAt(index);
+        }
+
+        @Override
+        public String toString()
+        {
+            return string.substring(0,index) + "|||" + string.substring(index);
+        }
+
+        public char[] scratchBuffer()
+        {
+            if (scratch == null)
+                scratch = new char[string.length()];
+            return scratch;
+        }
+    }
+
+    public static class ReaderSource implements Source
+    {
+        private Reader _reader;
+        private int _next = -1;
+        private char[] scratch;
+
+        public ReaderSource(Reader r)
+        {
+            _reader = r;
+        }
+
+        public void setReader(Reader reader)
+        {
+            _reader = reader;
+            _next = -1;
+        }
+
+        public boolean hasNext()
+        {
+            getNext();
+            if (_next < 0)
+            {
+                scratch = null;
+                return false;
+            }
+            return true;
+        }
+
+        public char next()
+        {
+            getNext();
+            char c = (char)_next;
+            _next = -1;
+            return c;
+        }
+
+        public char peek()
+        {
+            getNext();
+            return (char)_next;
+        }
+
+        private void getNext()
+        {
+            if (_next < 0)
+            {
+                try
+                {
+                    _next = _reader.read();
+                }
+                catch (IOException e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+
+        public char[] scratchBuffer()
+        {
+            if (scratch == null)
+                scratch = new char[1024];
+            return scratch;
+        }
+
+    }
+
+    /**
+     * JSON Output class for use by {@link Convertible}.
+     */
+    public interface Output
+    {
+        public void addClass(Class c);
+
+        public void add(Object obj);
+
+        public void add(String name, Object value);
+
+        public void add(String name, double value);
+
+        public void add(String name, long value);
+
+        public void add(String name, boolean value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * JSON Convertible object. Object can implement this interface in a similar
+     * way to the {@link Externalizable} interface is used to allow classes to
+     * provide their own serialization mechanism.
+     * <p>
+     * A JSON.Convertible object may be written to a JSONObject or initialized
+     * from a Map of field names to values.
+     * <p>
+     * If the JSON is to be convertible back to an Object, then the method
+     * {@link Output#addClass(Class)} must be called from within toJSON()
+     *
+     */
+    public interface Convertible
+    {
+        public void toJSON(Output out);
+
+        public void fromJSON(Map object);
+    }
+
+    /**
+     * Static JSON Convertor.
+     * <p>
+     * may be implemented to provide static convertors for objects that may be
+     * registered with
+     * {@link JSON#registerConvertor(Class, org.eclipse.jetty.util.ajax.JSON.Convertor)}
+     * . These convertors are looked up by class, interface and super class by
+     * {@link JSON#getConvertor(Class)}. Convertors should be used when the
+     * classes to be converted cannot implement {@link Convertible} or
+     * {@link Generator}.
+     */
+    public interface Convertor
+    {
+        public void toJSON(Object obj, Output out);
+
+        public Object fromJSON(Map object);
+    }
+
+    /**
+     * JSON Generator. A class that can add it's JSON representation directly to
+     * a StringBuffer. This is useful for object instances that are frequently
+     * converted and wish to avoid multiple Conversions
+     */
+    public interface Generator
+    {
+        public void addJSON(Appendable buffer);
+    }
+
+    /**
+     * A Literal JSON generator A utility instance of {@link JSON.Generator}
+     * that holds a pre-generated string on JSON text.
+     */
+    public static class Literal implements Generator
+    {
+        private String _json;
+
+        /**
+         * Construct a literal JSON instance for use by
+         * {@link JSON#toString(Object)}. If {@link Log#isDebugEnabled()} is
+         * true, the JSON will be parsed to check validity
+         *
+         * @param json
+         *            A literal JSON string.
+         */
+        public Literal(String json)
+        {
+            if (LOG.isDebugEnabled()) // TODO: Make this a configurable option on JSON instead!
+                parse(json);
+            _json = json;
+        }
+
+        @Override
+        public String toString()
+        {
+            return _json;
+        }
+
+        public void addJSON(Appendable buffer)
+        {
+            try
+            {
+                buffer.append(_json);
+            }
+            catch(IOException e)
+            {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java b/src/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
new file mode 100644
index 0000000..cd75cb3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONCollectionConvertor.java
@@ -0,0 +1,50 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Loader;
+
+public class JSONCollectionConvertor implements JSON.Convertor
+{
+    public void toJSON(Object obj, JSON.Output out)
+    {
+        out.addClass(obj.getClass());
+        out.add("list", ((Collection)obj).toArray());
+    }
+
+    public Object fromJSON(Map object)
+    {
+        try
+        {
+            Collection result = (Collection)Loader.loadClass(getClass(), (String)object.get("class")).newInstance();
+            Collections.addAll(result, (Object[])object.get("list"));
+            return result;
+        }
+        catch (Exception x)
+        {
+            if (x instanceof RuntimeException)
+                throw (RuntimeException)x;
+            throw new RuntimeException(x);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java b/src/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
new file mode 100644
index 0000000..e0c31ac
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONDateConvertor.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ajax;
+
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.eclipse.jetty.util.DateCache;
+import org.eclipse.jetty.util.ajax.JSON.Output;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+* Convert a {@link Date} to JSON.
+* If fromJSON is true in the constructor, the JSON generated will
+* be of the form {class="java.util.Date",value="1/1/1970 12:00 GMT"}
+* If fromJSON is false, then only the string value of the date is generated.
+*/
+public class JSONDateConvertor implements JSON.Convertor
+{
+    private static final Logger LOG = Log.getLogger(JSONDateConvertor.class);
+
+    private final boolean _fromJSON;
+    private final DateCache _dateCache;
+    private final SimpleDateFormat _format;
+
+    public JSONDateConvertor()
+    {
+        this(false);
+    }
+
+    public JSONDateConvertor(boolean fromJSON)
+    {
+        this(DateCache.DEFAULT_FORMAT,TimeZone.getTimeZone("GMT"),fromJSON);
+    }
+
+    public JSONDateConvertor(String format,TimeZone zone,boolean fromJSON)
+    {
+        _dateCache=new DateCache(format);
+        _dateCache.setTimeZone(zone);
+        _fromJSON=fromJSON;
+        _format=new SimpleDateFormat(format);
+        _format.setTimeZone(zone);
+    }
+
+    public JSONDateConvertor(String format, TimeZone zone, boolean fromJSON, Locale locale)
+    {
+        _dateCache = new DateCache(format, locale);
+        _dateCache.setTimeZone(zone);
+        _fromJSON = fromJSON;
+        _format = new SimpleDateFormat(format, new DateFormatSymbols(locale));
+        _format.setTimeZone(zone);
+    }
+
+    public Object fromJSON(Map map)
+    {
+        if (!_fromJSON)
+            throw new UnsupportedOperationException();
+        try
+        {
+            synchronized(_format)
+            {
+                return _format.parseObject((String)map.get("value"));
+            }
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+        return null;
+    }
+
+    public void toJSON(Object obj, Output out)
+    {
+        String date = _dateCache.format((Date)obj);
+        if (_fromJSON)
+        {
+            out.addClass(obj.getClass());
+            out.add("value",date);
+        }
+        else
+        {
+            out.add(date);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java b/src/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
new file mode 100644
index 0000000..9b63547
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONEnumConvertor.java
@@ -0,0 +1,93 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.ajax.JSON.Output;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Convert an {@link Enum} to JSON.
+ * If fromJSON is true in the constructor, the JSON generated will
+ * be of the form {class="com.acme.TrafficLight",value="Green"}
+ * If fromJSON is false, then only the string value of the enum is generated.
+ *
+ *
+ */
+public class JSONEnumConvertor implements JSON.Convertor
+{
+    private static final Logger LOG = Log.getLogger(JSONEnumConvertor.class);
+    private boolean _fromJSON;
+    private Method _valueOf;
+    {
+        try
+        {
+            Class<?> e = Loader.loadClass(getClass(),"java.lang.Enum");
+            _valueOf=e.getMethod("valueOf",Class.class,String.class);
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException("!Enums",e);
+        }
+    }
+
+    public JSONEnumConvertor()
+    {
+        this(false);
+    }
+
+    public JSONEnumConvertor(boolean fromJSON)
+    {
+        _fromJSON=fromJSON;
+    }
+
+    public Object fromJSON(Map map)
+    {
+        if (!_fromJSON)
+            throw new UnsupportedOperationException();
+        try
+        {
+            Class c=Loader.loadClass(getClass(),(String)map.get("class"));
+            return _valueOf.invoke(null,c,map.get("value"));
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+        return null;
+    }
+
+    public void toJSON(Object obj, Output out)
+    {
+        if (_fromJSON)
+        {
+            out.addClass(obj.getClass());
+            out.add("value",((Enum)obj).name());
+        }
+        else
+        {
+            out.add(((Enum)obj).name());
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java b/src/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
new file mode 100755
index 0000000..d0c222e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java
@@ -0,0 +1,115 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ajax.JSON.Output;
+
+/* ------------------------------------------------------------ */
+/**
+ * Convert an Object to JSON using reflection on getters methods.
+ * 
+ * 
+ *
+ */
+public class JSONObjectConvertor implements JSON.Convertor
+{
+    private boolean _fromJSON;
+    private Set _excluded=null;
+
+    public JSONObjectConvertor()
+    {
+        _fromJSON=false;
+    }
+    
+    public JSONObjectConvertor(boolean fromJSON)
+    {
+        _fromJSON=fromJSON;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param fromJSON
+     * @param excluded An array of field names to exclude from the conversion
+     */
+    public JSONObjectConvertor(boolean fromJSON,String[] excluded)
+    {
+        _fromJSON=fromJSON;
+        if (excluded!=null)
+            _excluded=new HashSet(Arrays.asList(excluded));
+    }
+
+    public Object fromJSON(Map map)
+    {
+        if (_fromJSON)
+            throw new UnsupportedOperationException();
+        return map;
+    }
+
+    public void toJSON(Object obj, Output out)
+    {
+        try
+        {
+            Class c=obj.getClass();
+
+            if (_fromJSON)
+                out.addClass(obj.getClass());
+
+            Method[] methods = obj.getClass().getMethods();
+
+            for (int i=0;i<methods.length;i++)
+            {
+                Method m=methods[i];
+                if (!Modifier.isStatic(m.getModifiers()) &&  
+                        m.getParameterTypes().length==0 && 
+                        m.getReturnType()!=null &&
+                        m.getDeclaringClass()!=Object.class)
+                {
+                    String name=m.getName();
+                    if (name.startsWith("is"))
+                        name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3);
+                    else if (name.startsWith("get"))
+                        name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
+                    else
+                        continue;
+
+                    if (includeField(name,obj,m))
+                        out.add(name, m.invoke(obj,(Object[])null));
+                }
+            }
+        } 
+        catch (Throwable e)
+        {
+            throw new IllegalArgumentException(e);
+        }
+    }
+    
+    protected boolean includeField(String name, Object o, Method m)
+    {
+        return _excluded==null || !_excluded.contains(name);
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java b/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
new file mode 100644
index 0000000..b1867cc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java
@@ -0,0 +1,431 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.util.ajax.JSON.Output;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+/**
+ * Converts POJOs to JSON and vice versa.
+ * The key difference:
+ *  - returns the actual object from Convertor.fromJSON (JSONObjectConverter returns a Map)
+ *  - the getters/setters are resolved at initialization (JSONObjectConverter resolves it at runtime)
+ *  - correctly sets the number fields
+ * 
+ */
+public class JSONPojoConvertor implements JSON.Convertor
+{
+    private static final Logger LOG = Log.getLogger(JSONPojoConvertor.class);
+    public static final Object[] GETTER_ARG = new Object[]{}, NULL_ARG = new Object[]{null};
+    private static final Map<Class<?>, NumberType> __numberTypes = new HashMap<Class<?>, NumberType>();
+    
+    public static NumberType getNumberType(Class<?> clazz)
+    {
+        return __numberTypes.get(clazz);
+    }
+    
+    protected boolean _fromJSON;
+    protected Class<?> _pojoClass;
+    protected Map<String,Method> _getters = new HashMap<String,Method>();
+    protected Map<String,Setter> _setters = new HashMap<String,Setter>();
+    protected Set<String> _excluded;
+
+    /**
+     * @param pojoClass The class to convert
+     */
+    public JSONPojoConvertor(Class<?> pojoClass)
+    {
+        this(pojoClass, (Set<String>)null, true);
+    }
+
+    /**
+     * @param pojoClass The class to convert
+     * @param excluded The fields to exclude
+     */
+    public JSONPojoConvertor(Class<?> pojoClass, String[] excluded)
+    {
+        this(pojoClass, new HashSet<String>(Arrays.asList(excluded)), true);
+    }
+
+    /**
+     * @param pojoClass The class to convert
+     * @param excluded The fields to exclude
+     */
+    public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded)
+    {
+        this(pojoClass, excluded, true);
+    }
+
+    /**
+     * @param pojoClass The class to convert
+     * @param excluded The fields to exclude
+     * @param fromJSON If true, add a class field to the JSON
+     */
+    public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded, boolean fromJSON)
+    {
+        _pojoClass = pojoClass;
+        _excluded = excluded;
+        _fromJSON = fromJSON;
+        init();
+    }    
+
+    /**
+     * @param pojoClass The class to convert
+     * @param fromJSON If true, add a class field to the JSON
+     */
+    public JSONPojoConvertor(Class<?> pojoClass, boolean fromJSON)
+    {
+        this(pojoClass, (Set<String>)null, fromJSON);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void init()
+    {
+        Method[] methods = _pojoClass.getMethods();
+        for (int i=0;i<methods.length;i++)
+        {
+            Method m=methods[i];
+            if (!Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass()!=Object.class)
+            {
+                String name=m.getName();
+                switch(m.getParameterTypes().length)
+                {
+                    case 0:
+                        
+                        if(m.getReturnType()!=null)
+                        {
+                            if (name.startsWith("is") && name.length()>2)
+                                name=name.substring(2,3).toLowerCase(Locale.ENGLISH)+name.substring(3);
+                            else if (name.startsWith("get") && name.length()>3)
+                                name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
+                            else 
+                                break;
+                            if(includeField(name, m))
+                                addGetter(name, m);
+                        }
+                        break;
+                    case 1:
+                        if (name.startsWith("set") && name.length()>3)
+                        {
+                            name=name.substring(3,4).toLowerCase(Locale.ENGLISH)+name.substring(4);
+                            if(includeField(name, m))
+                                addSetter(name, m);
+                        }
+                        break;                
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void addGetter(String name, Method method)
+    {
+        _getters.put(name, method);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void addSetter(String name, Method method)
+    {
+        _setters.put(name, new Setter(name, method));
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected Setter getSetter(String name)
+    {
+        return _setters.get(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected boolean includeField(String name, Method m)
+    {
+        return _excluded==null || !_excluded.contains(name);
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected int getExcludedCount()
+    {
+        return _excluded==null ? 0 : _excluded.size();
+    }
+
+    /* ------------------------------------------------------------ */
+    public Object fromJSON(Map object)
+    {        
+        Object obj = null;
+        try
+        {
+            obj = _pojoClass.newInstance();
+        }
+        catch(Exception e)
+        {
+            // TODO return Map instead?
+            throw new RuntimeException(e);
+        }
+        
+        setProps(obj, object);
+        return obj;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public int setProps(Object obj, Map<?,?> props)
+    {
+        int count = 0;
+        for(Iterator<?> iterator = props.entrySet().iterator(); iterator.hasNext();)
+        {
+            Map.Entry<?, ?> entry = (Map.Entry<?,?>) iterator.next();
+            Setter setter = getSetter((String)entry.getKey());
+            if(setter!=null)
+            {
+                try
+                {
+                    setter.invoke(obj, entry.getValue());                    
+                    count++;
+                }
+                catch(Exception e)
+                {
+                    // TODO throw exception?
+                    LOG.warn(_pojoClass.getName()+"#"+setter.getPropertyName()+" not set from "+
+                            (entry.getValue().getClass().getName())+"="+entry.getValue().toString());
+                    log(e);
+                }
+            }
+        }
+        return count;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void toJSON(Object obj, Output out)
+    {
+        if(_fromJSON)
+            out.addClass(_pojoClass);
+        for(Map.Entry<String,Method> entry : _getters.entrySet())
+        {            
+            try
+            {
+                out.add(entry.getKey(), entry.getValue().invoke(obj, GETTER_ARG));                    
+            }
+            catch(Exception e)
+            {
+                // TODO throw exception?
+                LOG.warn("{} property '{}' excluded. (errors)", _pojoClass.getName(), 
+                        entry.getKey());
+                log(e);
+            }
+        }        
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected void log(Throwable t)
+    {
+        LOG.ignore(t);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static class Setter
+    {
+        protected String _propertyName;
+        protected Method _setter;
+        protected NumberType _numberType;
+        protected Class<?> _type;
+        protected Class<?> _componentType;
+        
+        public Setter(String propertyName, Method method)
+        {
+            _propertyName = propertyName;
+            _setter = method;
+            _type = method.getParameterTypes()[0];
+            _numberType = __numberTypes.get(_type);
+            if(_numberType==null && _type.isArray())
+            {
+                _componentType = _type.getComponentType();
+                _numberType = __numberTypes.get(_componentType);
+            }
+        }
+        
+        public String getPropertyName()
+        {
+            return _propertyName;
+        }
+        
+        public Method getMethod()
+        {
+            return _setter;
+        }
+        
+        public NumberType getNumberType()
+        {
+            return _numberType;
+        }
+        
+        public Class<?> getType()
+        {
+            return _type;
+        }
+        
+        public Class<?> getComponentType()
+        {
+            return _componentType;
+        }
+        
+        public boolean isPropertyNumber()
+        {
+            return _numberType!=null;
+        }
+        
+        public void invoke(Object obj, Object value) throws IllegalArgumentException, 
+        IllegalAccessException, InvocationTargetException
+        {
+            if(value==null)
+                _setter.invoke(obj, NULL_ARG);
+            else
+                invokeObject(obj, value);
+        }
+        
+        protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, 
+            IllegalAccessException, InvocationTargetException
+        {
+            
+            if (_type.isEnum())
+            {
+                if (value instanceof Enum)
+                    _setter.invoke(obj, new Object[]{value});
+                else
+                    _setter.invoke(obj, new Object[]{Enum.valueOf((Class<? extends Enum>)_type,value.toString())});
+            }
+            else if(_numberType!=null && value instanceof Number)
+            {
+                _setter.invoke(obj, new Object[]{_numberType.getActualValue((Number)value)});
+            }
+            else if (Character.TYPE.equals(_type) || Character.class.equals(_type))
+            {
+                _setter.invoke(obj, new Object[]{String.valueOf(value).charAt(0)});
+            }
+            else if(_componentType!=null && value.getClass().isArray())
+            {
+                if(_numberType==null)
+                {
+                    int len = Array.getLength(value);
+                    Object array = Array.newInstance(_componentType, len);
+                    try
+                    {
+                        System.arraycopy(value, 0, array, 0, len);
+                    }
+                    catch(Exception e)
+                    {                        
+                        // unusual array with multiple types
+                        LOG.ignore(e);
+                        _setter.invoke(obj, new Object[]{value});
+                        return;
+                    }                    
+                    _setter.invoke(obj, new Object[]{array});
+                }
+                else
+                {
+                    Object[] old = (Object[])value;
+                    Object array = Array.newInstance(_componentType, old.length);
+                    try
+                    {
+                        for(int i=0; i<old.length; i++)
+                            Array.set(array, i, _numberType.getActualValue((Number)old[i]));
+                    }
+                    catch(Exception e)
+                    {                        
+                        // unusual array with multiple types
+                        LOG.ignore(e);
+                        _setter.invoke(obj, new Object[]{value});
+                        return;
+                    }
+                    _setter.invoke(obj, new Object[]{array});
+                }
+            }
+            else
+                _setter.invoke(obj, new Object[]{value});
+        }
+    }
+    
+    public interface NumberType
+    {        
+        public Object getActualValue(Number number);     
+    }
+    
+    public static final NumberType SHORT = new NumberType()
+    {
+        public Object getActualValue(Number number)
+        {            
+            return new Short(number.shortValue());
+        } 
+    };
+
+    public static final NumberType INTEGER = new NumberType()
+    {
+        public Object getActualValue(Number number)
+        {            
+            return new Integer(number.intValue());
+        }
+    };
+    
+    public static final NumberType FLOAT = new NumberType()
+    {
+        public Object getActualValue(Number number)
+        {            
+            return new Float(number.floatValue());
+        }      
+    };
+
+    public static final NumberType LONG = new NumberType()
+    {
+        public Object getActualValue(Number number)
+        {            
+            return number instanceof Long ? number : new Long(number.longValue());
+        }     
+    };
+
+    public static final NumberType DOUBLE = new NumberType()
+    {
+        public Object getActualValue(Number number)
+        {            
+            return number instanceof Double ? number : new Double(number.doubleValue());
+        }       
+    };
+
+    static
+    {
+        __numberTypes.put(Short.class, SHORT);
+        __numberTypes.put(Short.TYPE, SHORT);
+        __numberTypes.put(Integer.class, INTEGER);
+        __numberTypes.put(Integer.TYPE, INTEGER);
+        __numberTypes.put(Long.class, LONG);
+        __numberTypes.put(Long.TYPE, LONG);
+        __numberTypes.put(Float.class, FLOAT);
+        __numberTypes.put(Float.TYPE, FLOAT);
+        __numberTypes.put(Double.class, DOUBLE);
+        __numberTypes.put(Double.TYPE, DOUBLE);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java b/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java
new file mode 100644
index 0000000..18fc8ee
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ajax/JSONPojoConvertorFactory.java
@@ -0,0 +1,110 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      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.ajax;
+
+import java.util.Map;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.ajax.JSON.Convertor;
+import org.eclipse.jetty.util.ajax.JSON.Output;
+
+public class JSONPojoConvertorFactory implements JSON.Convertor
+{
+    private final JSON _json;
+    private final boolean _fromJson;
+
+    public JSONPojoConvertorFactory(JSON json)
+    {
+        if (json==null)
+        {
+            throw new IllegalArgumentException();
+        }
+        _json=json;
+        _fromJson=true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param json The JSON instance to use
+     * @param fromJSON If true, the class name of the objects is included
+     * in the generated JSON and is used to instantiate the object when
+     * JSON is parsed (otherwise a Map is used).
+     */
+    public JSONPojoConvertorFactory(JSON json,boolean fromJSON)
+    {
+        if (json==null)
+        {
+            throw new IllegalArgumentException();
+        }
+        _json=json;
+        _fromJson=fromJSON;
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void toJSON(Object obj, Output out)
+    {
+        String clsName=obj.getClass().getName();
+        Convertor convertor=_json.getConvertorFor(clsName);
+        if (convertor==null)
+        {
+            try
+            {
+                Class cls=Loader.loadClass(JSON.class,clsName);
+                convertor=new JSONPojoConvertor(cls,_fromJson);
+                _json.addConvertorFor(clsName, convertor);
+             }
+            catch (ClassNotFoundException e)
+            {
+                JSON.LOG.warn(e);
+            }
+        }
+        if (convertor!=null)
+        {
+            convertor.toJSON(obj, out);
+        }
+    }
+
+    public Object fromJSON(Map object)
+    {
+        Map map=object;
+        String clsName=(String)map.get("class");
+        if (clsName!=null)
+        {
+            Convertor convertor=_json.getConvertorFor(clsName);
+            if (convertor==null)
+            {
+                try
+                {
+                    Class cls=Loader.loadClass(JSON.class,clsName);
+                    convertor=new JSONPojoConvertor(cls,_fromJson);
+                    _json.addConvertorFor(clsName, convertor);
+                }
+                catch (ClassNotFoundException e)
+                {
+                    JSON.LOG.warn(e);
+                }
+            }
+            if (convertor!=null)
+            {
+                return convertor.fromJSON(object);
+            }
+        }
+        return map;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java b/src/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
new file mode 100644
index 0000000..ec3dd0e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java
@@ -0,0 +1,217 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Basic implementation of the life cycle interface for components.
+ * 
+ * 
+ */
+public abstract class AbstractLifeCycle implements LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(AbstractLifeCycle.class);
+    public static final String STOPPED="STOPPED";
+    public static final String FAILED="FAILED";
+    public static final String STARTING="STARTING";
+    public static final String STARTED="STARTED";
+    public static final String STOPPING="STOPPING";
+    public static final String RUNNING="RUNNING";
+    
+    private final Object _lock = new Object();
+    private final int __FAILED = -1, __STOPPED = 0, __STARTING = 1, __STARTED = 2, __STOPPING = 3;
+    private volatile int _state = __STOPPED;
+    
+    protected final CopyOnWriteArrayList<LifeCycle.Listener> _listeners=new CopyOnWriteArrayList<LifeCycle.Listener>();
+
+    protected void doStart() throws Exception
+    {
+    }
+
+    protected void doStop() throws Exception
+    {
+    }
+
+    public final void start() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STARTED || _state == __STARTING)
+                    return;
+                setStarting();
+                doStart();
+                setStarted();
+            }
+            catch (Exception e)
+            {
+                setFailed(e);
+                throw e;
+            }
+            catch (Error e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    public final void stop() throws Exception
+    {
+        synchronized (_lock)
+        {
+            try
+            {
+                if (_state == __STOPPING || _state == __STOPPED)
+                    return;
+                setStopping();
+                doStop();
+                setStopped();
+            }
+            catch (Exception e)
+            {
+                setFailed(e);
+                throw e;
+            }
+            catch (Error e)
+            {
+                setFailed(e);
+                throw e;
+            }
+        }
+    }
+
+    public boolean isRunning()
+    {
+        final int state = _state;
+        
+        return state == __STARTED || state == __STARTING;
+    }
+
+    public boolean isStarted()
+    {
+        return _state == __STARTED;
+    }
+
+    public boolean isStarting()
+    {
+        return _state == __STARTING;
+    }
+
+    public boolean isStopping()
+    {
+        return _state == __STOPPING;
+    }
+
+    public boolean isStopped()
+    {
+        return _state == __STOPPED;
+    }
+
+    public boolean isFailed()
+    {
+        return _state == __FAILED;
+    }
+
+    public void addLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.add(listener);
+    }
+
+    public void removeLifeCycleListener(LifeCycle.Listener listener)
+    {
+        _listeners.remove(listener);
+    }
+    
+    public String getState()
+    {
+        switch(_state)
+        {
+            case __FAILED: return FAILED;
+            case __STARTING: return STARTING;
+            case __STARTED: return STARTED;
+            case __STOPPING: return STOPPING;
+            case __STOPPED: return STOPPED;
+        }
+        return null;
+    }
+    
+    public static String getState(LifeCycle lc)
+    {
+        if (lc.isStarting()) return STARTING;
+        if (lc.isStarted()) return STARTED;
+        if (lc.isStopping()) return STOPPING;
+        if (lc.isStopped()) return STOPPED;
+        return FAILED;
+    }
+
+    private void setStarted()
+    {
+        _state = __STARTED;
+        LOG.debug(STARTED+" {}",this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarted(this);
+    }
+
+    private void setStarting()
+    {
+        LOG.debug("starting {}",this);
+        _state = __STARTING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStarting(this);
+    }
+
+    private void setStopping()
+    {
+        LOG.debug("stopping {}",this);
+        _state = __STOPPING;
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopping(this);
+    }
+
+    private void setStopped()
+    {
+        _state = __STOPPED;
+        LOG.debug("{} {}",STOPPED,this);
+        for (Listener listener : _listeners)
+            listener.lifeCycleStopped(this);
+    }
+
+    private void setFailed(Throwable th)
+    {
+        _state = __FAILED;
+        LOG.warn(FAILED+" " + this+": "+th,th);
+        for (Listener listener : _listeners)
+            listener.lifeCycleFailure(this,th);
+    }
+
+    public static abstract class AbstractLifeCycleListener implements LifeCycle.Listener
+    {
+        public void lifeCycleFailure(LifeCycle event, Throwable cause) {}
+        public void lifeCycleStarted(LifeCycle event) {}
+        public void lifeCycleStarting(LifeCycle event) {}
+        public void lifeCycleStopped(LifeCycle event) {}
+        public void lifeCycleStopping(LifeCycle event) {}
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java b/src/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
new file mode 100644
index 0000000..1eb22dd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/AggregateLifeCycle.java
@@ -0,0 +1,441 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * An AggregateLifeCycle is an {@link LifeCycle} implementation for a collection of contained beans.
+ * <p>
+ * Beans can be added the AggregateLifeCycle either as managed beans or as unmanaged beans.  A managed bean is started, stopped and destroyed with the aggregate.  
+ * An unmanaged bean is associated with the aggregate for the purposes of {@link #dump()}, but it's lifecycle must be managed externally.
+ * <p>
+ * When a bean is added, if it is a {@link LifeCycle} and it is already started, then it is assumed to be an unmanaged bean.  
+ * Otherwise the methods {@link #addBean(Object, boolean)}, {@link #manage(Object)} and {@link #unmanage(Object)} can be used to 
+ * explicitly control the life cycle relationship.
+ * <p>
+ * If adding a bean that is shared between multiple {@link AggregateLifeCycle} instances, then it should be started before being added, so it is unmanaged, or 
+ * the API must be used to explicitly set it as unmanaged.
+ * <p>
+ */
+public class AggregateLifeCycle extends AbstractLifeCycle implements Destroyable, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(AggregateLifeCycle.class);
+    private final List<Bean> _beans=new CopyOnWriteArrayList<Bean>();
+    private boolean _started=false;
+
+    private class Bean
+    {
+        Bean(Object b) 
+        {
+            _bean=b;
+        }
+        final Object _bean;
+        volatile boolean _managed=true;
+        
+        public String toString()
+        {
+            return "{"+_bean+","+_managed+"}";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Start the managed lifecycle beans in the order they were added.
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        for (Bean b:_beans)
+        {
+            if (b._managed && b._bean instanceof LifeCycle)
+            {
+                LifeCycle l=(LifeCycle)b._bean;
+                if (!l.isRunning())
+                    l.start();
+            }
+        }
+        // indicate that we are started, so that addBean will start other beans added.
+        _started=true;
+        super.doStart();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Stop the joined lifecycle beans in the reverse order they were added.
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        _started=false;
+        super.doStop();
+        List<Bean> reverse = new ArrayList<Bean>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b:reverse)
+        {
+            if (b._managed && b._bean instanceof LifeCycle)
+            {
+                LifeCycle l=(LifeCycle)b._bean;
+                if (l.isRunning())
+                    l.stop();
+            }
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Destroy the joined Destroyable beans in the reverse order they were added.
+     * @see org.eclipse.jetty.util.component.Destroyable#destroy()
+     */
+    public void destroy()
+    {
+        List<Bean> reverse = new ArrayList<Bean>(_beans);
+        Collections.reverse(reverse);
+        for (Bean b:reverse)
+        {
+            if (b._bean instanceof Destroyable && b._managed)
+            {
+                Destroyable d=(Destroyable)b._bean;
+                d.destroy();
+            }
+        }
+        _beans.clear();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Is the bean contained in the aggregate.
+     * @param bean
+     * @return True if the aggregate contains the bean
+     */
+    public boolean contains(Object bean)
+    {
+        for (Bean b:_beans)
+            if (b._bean==bean)
+                return true;
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Is the bean joined to the aggregate.
+     * @param bean
+     * @return True if the aggregate contains the bean and it is joined
+     */
+    public boolean isManaged(Object bean)
+    {
+        for (Bean b:_beans)
+            if (b._bean==bean)
+                return b._managed;
+        return false;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Add an associated bean.
+     * If the bean is a {@link LifeCycle}, then it will be managed if it is not 
+     * already started and umanaged if it is already started. The {@link #addBean(Object, boolean)}
+     * method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
+     * methods may be used after an add to change the status.
+     * @param o the bean object to add
+     * @return true if the bean was added or false if it has already been added.
+     */
+    public boolean addBean(Object o)
+    {
+        // beans are joined unless they are started lifecycles
+        return addBean(o,!((o instanceof LifeCycle)&&((LifeCycle)o).isStarted()));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add an associated lifecycle.
+     * @param o The lifecycle to add
+     * @param managed True if the LifeCycle is to be joined, otherwise it will be disjoint.
+     * @return true if bean was added, false if already present.
+     */
+    public boolean addBean(Object o, boolean managed)
+    {
+        if (contains(o))
+            return false;
+        
+        Bean b = new Bean(o);
+        b._managed=managed;
+        _beans.add(b);
+        
+        if (o instanceof LifeCycle)
+        {
+            LifeCycle l=(LifeCycle)o;
+
+            // Start the bean if we are started
+            if (managed && _started)
+            {
+                try
+                {
+                    l.start();
+                }
+                catch(Exception e)
+                {
+                    throw new RuntimeException (e);
+                }
+            }
+        }
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Manage a bean by this aggregate, so that it is started/stopped/destroyed with the 
+     * aggregate lifecycle.  
+     * @param bean The bean to manage (must already have been added).
+     */
+    public void manage(Object bean)
+    {    
+        for (Bean b :_beans)
+        {
+            if (b._bean==bean)
+            {
+                b._managed=true;
+                return;
+            }
+        }
+        throw new IllegalArgumentException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unmanage a bean by this aggregate, so that it is not started/stopped/destroyed with the 
+     * aggregate lifecycle.  
+     * @param bean The bean to manage (must already have been added).
+     */
+    public void unmanage(Object bean)
+    {
+        for (Bean b :_beans)
+        {
+            if (b._bean==bean)
+            {
+                b._managed=false;
+                return;
+            }
+        }
+        throw new IllegalArgumentException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get dependent beans 
+     * @return List of beans.
+     */
+    public Collection<Object> getBeans()
+    {
+        return getBeans(Object.class);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get dependent beans of a specific class
+     * @see #addBean(Object)
+     * @param clazz
+     * @return List of beans.
+     */
+    public <T> List<T> getBeans(Class<T> clazz)
+    {
+        ArrayList<T> beans = new ArrayList<T>();
+        for (Bean b:_beans)
+        {
+            if (clazz.isInstance(b._bean))
+                beans.add((T)(b._bean));
+        }
+        return beans;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /** Get dependent beans of a specific class.
+     * If more than one bean of the type exist, the first is returned.
+     * @see #addBean(Object)
+     * @param clazz
+     * @return bean or null
+     */
+    public <T> T getBean(Class<T> clazz)
+    {
+        for (Bean b:_beans)
+        {
+            if (clazz.isInstance(b._bean))
+                return (T)b._bean;
+        }
+        
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove all associated bean.
+     */
+    public void removeBeans ()
+    {
+        _beans.clear();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Remove an associated bean.
+     */
+    public boolean removeBean (Object o)
+    {
+        Iterator<Bean> i = _beans.iterator();
+        while(i.hasNext())
+        {
+            Bean b=i.next();
+            if (b._bean==o)
+            {
+                _beans.remove(b);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dumpStdErr()
+    {
+        try
+        {
+            dump(System.err,"");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return dump(this);
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public static String dump(Dumpable dumpable)
+    {
+        StringBuilder b = new StringBuilder();
+        try
+        {
+            dumpable.dump(b,"");
+        }
+        catch (IOException e)
+        {
+            LOG.warn(e);
+        }
+        return b.toString();
+    }    
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out) throws IOException
+    {
+        dump(out,"");
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void dumpThis(Appendable out) throws IOException
+    {
+        out.append(String.valueOf(this)).append(" - ").append(getState()).append("\n");
+    }
+
+    /* ------------------------------------------------------------ */
+    public static void dumpObject(Appendable out,Object o) throws IOException
+    {
+        try
+        {
+            if (o instanceof LifeCycle)
+                out.append(String.valueOf(o)).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n");
+            else
+                out.append(String.valueOf(o)).append("\n");
+        }
+        catch(Throwable th)
+        {
+            out.append(" => ").append(th.toString()).append('\n');
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out,String indent) throws IOException
+    {
+        dumpThis(out);
+        int size=_beans.size();
+        if (size==0)
+            return;
+        int i=0;
+        for (Bean b : _beans)
+        {
+            i++;
+
+            out.append(indent).append(" +- ");
+            if (b._managed)
+            {
+                if (b._bean instanceof Dumpable)
+                    ((Dumpable)b._bean).dump(out,indent+(i==size?"    ":" |  "));
+                else 
+                    dumpObject(out,b._bean);
+            }
+            else 
+                dumpObject(out,b._bean);
+        }
+
+        if (i!=size)
+            out.append(indent).append(" |\n");
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static void dump(Appendable out,String indent,Collection<?>... collections) throws IOException
+    {
+        if (collections.length==0)
+            return;
+        int size=0;
+        for (Collection<?> c : collections)
+            size+=c.size();    
+        if (size==0)
+            return;
+
+        int i=0;
+        for (Collection<?> c : collections)
+        {
+            for (Object o : c)
+            {
+                i++;
+                out.append(indent).append(" +- ");
+
+                if (o instanceof Dumpable)
+                    ((Dumpable)o).dump(out,indent+(i==size?"    ":" |  "));
+                else 
+                    dumpObject(out,o);
+            }
+            
+            if (i!=size)
+                out.append(indent).append(" |\n");          
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/component/Container.java b/src/java/org/eclipse/jetty/util/component/Container.java
new file mode 100644
index 0000000..a83064a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/Container.java
@@ -0,0 +1,305 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+import java.lang.ref.WeakReference;
+import java.util.EventListener;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Container.
+ * This class allows a containment events to be generated from update methods.
+ * 
+ * The style of usage is: <pre>
+ *   public void setFoo(Foo foo)
+ *   {
+ *       getContainer().update(this,this.foo,foo,"foo");
+ *       this.foo=foo;
+ *   }
+ *   
+ *   public void setBars(Bar[] bars)
+ *   {
+ *       getContainer().update(this,this.bars,bars,"bar");
+ *       this.bars=bars;
+ *   }
+ * </pre>
+ */
+public class Container
+{
+    private static final Logger LOG = Log.getLogger(Container.class);
+    private final CopyOnWriteArrayList<Container.Listener> _listeners=new CopyOnWriteArrayList<Container.Listener>();
+    
+    public void addEventListener(Container.Listener listener)
+    {
+        _listeners.add(listener);
+    }
+    
+    public void removeEventListener(Container.Listener listener)
+    {
+        _listeners.remove(listener);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Update single parent to child relationship.
+     * @param parent The parent of the child.
+     * @param oldChild The previous value of the child.  If this is non null and differs from <code>child</code>, then a remove event is generated.
+     * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated.
+     * @param relationship The name of the relationship
+     */
+    public void update(Object parent, Object oldChild, final Object child, String relationship)
+    {
+        if (oldChild!=null && !oldChild.equals(child))
+            remove(parent,oldChild,relationship);
+        if (child!=null && !child.equals(oldChild))
+            add(parent,child,relationship);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Update single parent to child relationship.
+     * @param parent The parent of the child.
+     * @param oldChild The previous value of the child.  If this is non null and differs from <code>child</code>, then a remove event is generated.
+     * @param child The current child. If this is non null and differs from <code>oldChild</code>, then an add event is generated.
+     * @param relationship The name of the relationship
+     * @param addRemove If true add/remove is called for the new/old children as well as the relationships
+     */
+    public void update(Object parent, Object oldChild, final Object child, String relationship,boolean addRemove)
+    {
+        if (oldChild!=null && !oldChild.equals(child))
+        {
+            remove(parent,oldChild,relationship);
+            if (addRemove)
+                removeBean(oldChild);
+        }
+        
+        if (child!=null && !child.equals(oldChild))
+        {
+            if (addRemove)
+                addBean(child);
+            add(parent,child,relationship);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Update multiple parent to child relationship.
+     * @param parent The parent of the child.
+     * @param oldChildren The previous array of children.  A remove event is generated for any child in this array but not in the  <code>children</code> array.
+     * This array is modified and children that remain in the new children array are nulled out of the old children array.
+     * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array.
+     * @param relationship The name of the relationship
+     */
+    public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship)
+    {
+        update(parent,oldChildren,children,relationship,false);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Update multiple parent to child relationship.
+     * @param parent The parent of the child.
+     * @param oldChildren The previous array of children.  A remove event is generated for any child in this array but not in the  <code>children</code> array.
+     * This array is modified and children that remain in the new children array are nulled out of the old children array.
+     * @param children The current array of children. An add event is generated for any child in this array but not in the <code>oldChildren</code> array.
+     * @param relationship The name of the relationship
+     * @param addRemove If true add/remove is called for the new/old children as well as the relationships
+     */
+    public void update(Object parent, Object[] oldChildren, final Object[] children, String relationship, boolean addRemove)
+    {
+        Object[] newChildren = null;
+        if (children!=null)
+        {
+            newChildren = new Object[children.length];
+        
+            for (int i=children.length;i-->0;)
+            {
+                boolean new_child=true;
+                if (oldChildren!=null)
+                {
+                    for (int j=oldChildren.length;j-->0;)
+                    {
+                        if (children[i]!=null && children[i].equals(oldChildren[j]))
+                        {
+                            oldChildren[j]=null;
+                            new_child=false;
+                        }
+                    }
+                }
+                if (new_child)
+                    newChildren[i]=children[i];
+            }
+        }
+        
+        if (oldChildren!=null)
+        {
+            for (int i=oldChildren.length;i-->0;)
+            {
+                if (oldChildren[i]!=null)
+                {
+                    remove(parent,oldChildren[i],relationship);
+                    if (addRemove)
+                        removeBean(oldChildren[i]);
+                }
+            }
+        }
+        
+        if (newChildren!=null)
+        {
+            for (int i=0;i<newChildren.length;i++)
+                if (newChildren[i]!=null)
+                {
+                    if (addRemove)
+                        addBean(newChildren[i]);
+                    add(parent,newChildren[i],relationship);
+                }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addBean(Object obj)
+    {
+        if (_listeners!=null)
+        {
+            for (int i=0; i<LazyList.size(_listeners); i++)
+            {
+                Listener listener=(Listener)LazyList.get(_listeners, i);
+                listener.addBean(obj);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void removeBean(Object obj)
+    {
+        if (_listeners!=null)
+        {
+            for (int i=0; i<LazyList.size(_listeners); i++)
+                ((Listener)LazyList.get(_listeners, i)).removeBean(obj);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add a parent child relationship
+     * @param parent
+     * @param child
+     * @param relationship
+     */
+    private void add(Object parent, Object child, String relationship)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Container "+parent+" + "+child+" as "+relationship);
+        if (_listeners!=null)
+        {
+            Relationship event=new Relationship(this,parent,child,relationship);
+            for (int i=0; i<LazyList.size(_listeners); i++)
+                ((Listener)LazyList.get(_listeners, i)).add(event);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** remove a parent child relationship
+     * @param parent
+     * @param child
+     * @param relationship
+     */
+    private void remove(Object parent, Object child, String relationship)
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("Container "+parent+" - "+child+" as "+relationship);
+        if (_listeners!=null)
+        {
+            Relationship event=new Relationship(this,parent,child,relationship);
+            for (int i=0; i<LazyList.size(_listeners); i++)
+                ((Listener)LazyList.get(_listeners, i)).remove(event);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** A Container event.
+     * @see Listener
+     */
+    public static class Relationship
+    {
+        private final WeakReference<Object> _parent;
+        private final WeakReference<Object> _child;
+        private String _relationship;
+        private Container _container;
+        
+        private Relationship(Container container, Object parent,Object child, String relationship)
+        {
+            _container=container;
+            _parent=new WeakReference<Object>(parent);
+            _child=new WeakReference<Object>(child);
+            _relationship=relationship;
+        }
+        
+        public Container getContainer()
+        {
+            return _container;
+        }
+        
+        public Object getChild()
+        {
+            return _child.get();
+        }
+        
+        public Object getParent()
+        {
+            return _parent.get();
+        }
+        
+        public String getRelationship()
+        {
+            return _relationship;
+        }
+        
+        @Override
+        public String toString()
+        {
+            return _parent+"---"+_relationship+"-->"+_child;
+        }
+        
+        @Override
+        public int hashCode()
+        {
+            return _parent.hashCode()+_child.hashCode()+_relationship.hashCode();
+        }
+        
+        @Override
+        public boolean equals(Object o)
+        {
+            if (o==null || !(o instanceof Relationship))
+                return false;
+            Relationship r = (Relationship)o;
+            return r._parent.get()==_parent.get() && r._child.get()==_child.get() && r._relationship.equals(_relationship);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Listener.
+     * A listener for Container events.
+     */
+    public interface Listener extends EventListener
+    {
+        public void addBean(Object bean);
+        public void removeBean(Object bean);
+        public void add(Container.Relationship relationship);
+        public void remove(Container.Relationship relationship);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/component/Destroyable.java b/src/java/org/eclipse/jetty/util/component/Destroyable.java
new file mode 100644
index 0000000..2e7e441
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/Destroyable.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+
+/**
+ * <p>A Destroyable is an object which can be destroyed.</p>
+ * <p>Typically a Destroyable is a {@link LifeCycle} component that can hold onto
+ * resources over multiple start/stop cycles.   A call to destroy will release all
+ * resources and will prevent any further start/stop cycles from being successful.</p>
+ */
+public interface Destroyable
+{
+    void destroy();
+}
diff --git a/src/java/org/eclipse/jetty/util/component/Dumpable.java b/src/java/org/eclipse/jetty/util/component/Dumpable.java
new file mode 100644
index 0000000..104fb08
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/Dumpable.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.IOException;
+
+public interface Dumpable
+{
+    String dump();
+    void dump(Appendable out,String indent) throws IOException;
+}
diff --git a/src/java/org/eclipse/jetty/util/component/FileDestroyable.java b/src/java/org/eclipse/jetty/util/component/FileDestroyable.java
new file mode 100644
index 0000000..1da4e8d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/FileDestroyable.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+public class FileDestroyable implements Destroyable
+{
+    private static final Logger LOG = Log.getLogger(FileDestroyable.class);
+    final List<File> _files = new ArrayList<File>();
+
+    public FileDestroyable()
+    {
+    }
+    
+    public FileDestroyable(String file) throws IOException
+    {
+        _files.add(Resource.newResource(file).getFile());
+    }
+    
+    public FileDestroyable(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFile(String file) throws IOException
+    {
+        _files.add(Resource.newResource(file).getFile());
+    }
+    
+    public void addFile(File file)
+    {
+        _files.add(file);
+    }
+    
+    public void addFiles(Collection<File> files)
+    {
+        _files.addAll(files);
+    }
+    
+    public void removeFile(String file) throws IOException
+    {
+        _files.remove(Resource.newResource(file).getFile());
+    }
+    
+    public void removeFile(File file)
+    {
+        _files.remove(file);
+    }
+    
+    public void destroy()
+    {
+        for (File file : _files)
+        {
+            if (file.exists())
+            {
+                LOG.debug("Destroy {}",file);
+                IO.delete(file);
+            }
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java b/src/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
new file mode 100644
index 0000000..0f1ec44
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/FileNoticeLifeCycleListener.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.io.FileWriter;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A LifeCycle Listener that writes state changes to a file.
+ * <p>This can be used with the jetty.sh script to wait for successful startup.
+ */
+public class FileNoticeLifeCycleListener implements LifeCycle.Listener
+{
+    Logger LOG = Log.getLogger(FileNoticeLifeCycleListener.class);
+    
+    private final String _filename;
+    
+    public FileNoticeLifeCycleListener(String filename)
+    {
+        _filename=filename;
+    }
+
+    private void writeState(String action, LifeCycle lifecycle)
+    {
+        try
+        {
+            FileWriter out = new FileWriter(_filename,true);
+            out.append(action).append(" ").append(lifecycle.toString()).append("\n");
+            out.close();
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    public void lifeCycleStarting(LifeCycle event)
+    {  
+        writeState("STARTING",event);      
+    }
+
+    public void lifeCycleStarted(LifeCycle event)
+    {        
+        writeState("STARTED",event); 
+    }
+
+    public void lifeCycleFailure(LifeCycle event, Throwable cause)
+    {        
+        writeState("FAILED",event);
+    }
+
+    public void lifeCycleStopping(LifeCycle event)
+    {        
+        writeState("STOPPING",event);
+    }
+
+    public void lifeCycleStopped(LifeCycle event)
+    {        
+        writeState("STOPPED",event);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/component/LifeCycle.java b/src/java/org/eclipse/jetty/util/component/LifeCycle.java
new file mode 100644
index 0000000..1d58e3e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/component/LifeCycle.java
@@ -0,0 +1,119 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.component;
+
+import java.util.EventListener;
+
+/* ------------------------------------------------------------ */
+/**
+ * The lifecycle interface for generic components.
+ * <br />
+ * Classes implementing this interface have a defined life cycle
+ * defined by the methods of this interface.
+ *
+ * 
+ */
+public interface LifeCycle
+{
+    /* ------------------------------------------------------------ */
+    /**
+     * Starts the component.
+     * @throws Exception If the component fails to start
+     * @see #isStarted()
+     * @see #stop()
+     * @see #isFailed()
+     */
+    public void start()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Stops the component.
+     * The component may wait for current activities to complete
+     * normally, but it can be interrupted.
+     * @exception Exception If the component fails to stop
+     * @see #isStopped()
+     * @see #start()
+     * @see #isFailed()
+     */
+    public void stop()
+        throws Exception;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting or has been started.
+     */
+    public boolean isRunning();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been started.
+     * @see #start()
+     * @see #isStarting()
+     */
+    public boolean isStarted();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is starting.
+     * @see #isStarted()
+     */
+    public boolean isStarting();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component is stopping.
+     * @see #isStopped()
+     */
+    public boolean isStopping();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has been stopped.
+     * @see #stop()
+     * @see #isStopping()
+     */
+    public boolean isStopped();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if the component has failed to start or has failed to stop.
+     */
+    public boolean isFailed();
+    
+    /* ------------------------------------------------------------ */
+    public void addLifeCycleListener(LifeCycle.Listener listener);
+
+    /* ------------------------------------------------------------ */
+    public void removeLifeCycleListener(LifeCycle.Listener listener);
+    
+
+    /* ------------------------------------------------------------ */
+    /** Listener.
+     * A listener for Lifecycle events.
+     */
+    public interface Listener extends EventListener
+    {
+        public void lifeCycleStarting(LifeCycle event);
+        public void lifeCycleStarted(LifeCycle event);
+        public void lifeCycleFailure(LifeCycle event,Throwable cause);
+        public void lifeCycleStopping(LifeCycle event);
+        public void lifeCycleStopped(LifeCycle event);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/AbstractLogger.java b/src/java/org/eclipse/jetty/util/log/AbstractLogger.java
new file mode 100644
index 0000000..2441bb5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/AbstractLogger.java
@@ -0,0 +1,77 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+
+/* ------------------------------------------------------------ */
+/** Abstract Logger.
+ * Manages the atomic registration of the logger by name.
+ */
+public abstract class AbstractLogger implements Logger
+{
+    public final Logger getLogger(String name)
+    {
+        if (isBlank(name))
+            return this;
+
+        final String basename = getName();
+        final String fullname = (isBlank(basename) || Log.getRootLogger()==this)?name:(basename + "." + name);
+        
+        Logger logger = Log.getLoggers().get(fullname);
+        if (logger == null)
+        {
+            Logger newlog = newLogger(fullname);
+            
+            logger = Log.getMutableLoggers().putIfAbsent(fullname,newlog);
+            if (logger == null)
+                logger=newlog;
+        }
+
+        return logger;
+    }
+    
+
+    protected abstract Logger newLogger(String fullname);
+
+    /**
+     * A more robust form of name blank test. Will return true for null names, and names that have only whitespace
+     *
+     * @param name
+     *            the name to test
+     * @return true for null or blank name, false if any non-whitespace character is found.
+     */
+    private static boolean isBlank(String name)
+    {
+        if (name == null)
+        {
+            return true;
+        }
+        int size = name.length();
+        char c;
+        for (int i = 0; i < size; i++)
+        {
+            c = name.charAt(i);
+            if (!Character.isWhitespace(c))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/JavaUtilLog.java b/src/java/org/eclipse/jetty/util/log/JavaUtilLog.java
new file mode 100644
index 0000000..b79d7a1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/JavaUtilLog.java
@@ -0,0 +1,163 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.util.logging.Level;
+
+/**
+ * <p>
+ * Implementation of Jetty {@link Logger} based on {@link java.util.logging.Logger}.
+ * </p>
+ *
+ * <p>
+ * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html">
+ * standard java.util.logging configuration</a>.
+ * </p>
+ */
+public class JavaUtilLog extends AbstractLogger
+{
+    private Level configuredLevel;
+    private java.util.logging.Logger _logger;
+
+    public JavaUtilLog()
+    {
+        this("org.eclipse.jetty.util.log");
+    }
+
+    public JavaUtilLog(String name)
+    {
+        _logger = java.util.logging.Logger.getLogger(name);
+        if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false")))
+        {
+            _logger.setLevel(Level.FINE);
+        }
+        configuredLevel = _logger.getLevel();
+    }
+
+    public String getName()
+    {
+        return _logger.getName();
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        _logger.log(Level.WARNING, format(msg, args));
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        _logger.log(Level.WARNING, msg, thrown);
+    }
+
+    public void info(String msg, Object... args)
+    {
+        _logger.log(Level.INFO, format(msg, args));
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        _logger.log(Level.INFO, msg, thrown);
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _logger.isLoggable(Level.FINE);
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            configuredLevel = _logger.getLevel();
+            _logger.setLevel(Level.FINE);
+        }
+        else
+        {
+            _logger.setLevel(configuredLevel);
+        }
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        _logger.log(Level.FINE, format(msg, args));
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        _logger.log(Level.FINE, msg, thrown);
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        return new JavaUtilLog(fullname);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    private String format(String msg, Object... args)
+    {
+        msg = String.valueOf(msg); // Avoids NPE
+        String braces = "{}";
+        StringBuilder builder = new StringBuilder();
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces, start);
+            if (bracesIndex < 0)
+            {
+                builder.append(msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                builder.append(msg.substring(start, bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        builder.append(msg.substring(start));
+        return builder.toString();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/JettyAwareLogger.java b/src/java/org/eclipse/jetty/util/log/JettyAwareLogger.java
new file mode 100644
index 0000000..fb0ea18
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/JettyAwareLogger.java
@@ -0,0 +1,624 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import org.slf4j.Marker;
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MessageFormatter;
+
+/**
+ * JettyAwareLogger is used to fix a FQCN bug that arises from how Jetty
+ * Log uses an indirect slf4j implementation.
+ *
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670
+ */
+class JettyAwareLogger implements org.slf4j.Logger
+{
+    private static final int DEBUG = org.slf4j.spi.LocationAwareLogger.DEBUG_INT;
+    private static final int ERROR = org.slf4j.spi.LocationAwareLogger.ERROR_INT;
+    private static final int INFO  = org.slf4j.spi.LocationAwareLogger.INFO_INT;
+    private static final int TRACE = org.slf4j.spi.LocationAwareLogger.TRACE_INT;
+    private static final int WARN  = org.slf4j.spi.LocationAwareLogger.WARN_INT;
+
+    private static final String FQCN = Slf4jLog.class.getName();
+    private final org.slf4j.spi.LocationAwareLogger _logger;
+
+    public JettyAwareLogger(org.slf4j.spi.LocationAwareLogger logger)
+    {
+        _logger = logger;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#getName()
+     */
+    public String getName()
+    {
+        return _logger.getName();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isTraceEnabled()
+     */
+    public boolean isTraceEnabled()
+    {
+        return _logger.isTraceEnabled();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(java.lang.String)
+     */
+    public void trace(String msg)
+    {
+        log(null, TRACE, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object)
+     */
+    public void trace(String format, Object arg)
+    {
+        log(null, TRACE, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void trace(String format, Object arg1, Object arg2)
+    {
+        log(null, TRACE, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Object[])
+     */
+    public void trace(String format, Object[] argArray)
+    {
+        log(null, TRACE, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(java.lang.String, java.lang.Throwable)
+     */
+    public void trace(String msg, Throwable t)
+    {
+        log(null, TRACE, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isTraceEnabled(org.slf4j.Marker)
+     */
+    public boolean isTraceEnabled(Marker marker)
+    {
+        return _logger.isTraceEnabled(marker);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String)
+     */
+    public void trace(Marker marker, String msg)
+    {
+        log(marker, TRACE, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object)
+     */
+    public void trace(Marker marker, String format, Object arg)
+    {
+        log(marker, TRACE, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void trace(Marker marker, String format, Object arg1, Object arg2)
+    {
+        log(marker, TRACE, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Object[])
+     */
+    public void trace(Marker marker, String format, Object[] argArray)
+    {
+        log(marker, TRACE, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#trace(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
+     */
+    public void trace(Marker marker, String msg, Throwable t)
+    {
+        log(marker, TRACE, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isDebugEnabled()
+     */
+    public boolean isDebugEnabled()
+    {
+        return _logger.isDebugEnabled();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(java.lang.String)
+     */
+    public void debug(String msg)
+    {
+        log(null, DEBUG, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object)
+     */
+    public void debug(String format, Object arg)
+    {
+        log(null, DEBUG, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void debug(String format, Object arg1, Object arg2)
+    {
+        log(null, DEBUG, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Object[])
+     */
+    public void debug(String format, Object[] argArray)
+    {
+        log(null, DEBUG, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(java.lang.String, java.lang.Throwable)
+     */
+    public void debug(String msg, Throwable t)
+    {
+        log(null, DEBUG, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isDebugEnabled(org.slf4j.Marker)
+     */
+    public boolean isDebugEnabled(Marker marker)
+    {
+        return _logger.isDebugEnabled(marker);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String)
+     */
+    public void debug(Marker marker, String msg)
+    {
+        log(marker, DEBUG, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object)
+     */
+    public void debug(Marker marker, String format, Object arg)
+    {
+        log(marker, DEBUG, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void debug(Marker marker, String format, Object arg1, Object arg2)
+    {
+        log(marker, DEBUG, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Object[])
+     */
+    public void debug(Marker marker, String format, Object[] argArray)
+    {
+        log(marker, DEBUG, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#debug(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
+     */
+    public void debug(Marker marker, String msg, Throwable t)
+    {
+        log(marker, DEBUG, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isInfoEnabled()
+     */
+    public boolean isInfoEnabled()
+    {
+        return _logger.isInfoEnabled();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(java.lang.String)
+     */
+    public void info(String msg)
+    {
+        log(null, INFO, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object)
+     */
+    public void info(String format, Object arg)
+    {
+        log(null, INFO, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void info(String format, Object arg1, Object arg2)
+    {
+        log(null, INFO, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(java.lang.String, java.lang.Object[])
+     */
+    public void info(String format, Object[] argArray)
+    {
+        log(null, INFO, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(java.lang.String, java.lang.Throwable)
+     */
+    public void info(String msg, Throwable t)
+    {
+        log(null, INFO, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isInfoEnabled(org.slf4j.Marker)
+     */
+    public boolean isInfoEnabled(Marker marker)
+    {
+        return _logger.isInfoEnabled(marker);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String)
+     */
+    public void info(Marker marker, String msg)
+    {
+        log(marker, INFO, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object)
+     */
+    public void info(Marker marker, String format, Object arg)
+    {
+        log(marker, INFO, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void info(Marker marker, String format, Object arg1, Object arg2)
+    {
+        log(marker, INFO, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Object[])
+     */
+    public void info(Marker marker, String format, Object[] argArray)
+    {
+        log(marker, INFO, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#info(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
+     */
+    public void info(Marker marker, String msg, Throwable t)
+    {
+        log(marker, INFO, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isWarnEnabled()
+     */
+    public boolean isWarnEnabled()
+    {
+        return _logger.isWarnEnabled();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(java.lang.String)
+     */
+    public void warn(String msg)
+    {
+        log(null, WARN, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object)
+     */
+    public void warn(String format, Object arg)
+    {
+        log(null, WARN, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object[])
+     */
+    public void warn(String format, Object[] argArray)
+    {
+        log(null, WARN, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void warn(String format, Object arg1, Object arg2)
+    {
+        log(null, WARN, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(java.lang.String, java.lang.Throwable)
+     */
+    public void warn(String msg, Throwable t)
+    {
+        log(null, WARN, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isWarnEnabled(org.slf4j.Marker)
+     */
+    public boolean isWarnEnabled(Marker marker)
+    {
+        return _logger.isWarnEnabled(marker);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String)
+     */
+    public void warn(Marker marker, String msg)
+    {
+        log(marker, WARN, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object)
+     */
+    public void warn(Marker marker, String format, Object arg)
+    {
+        log(marker, WARN, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void warn(Marker marker, String format, Object arg1, Object arg2)
+    {
+        log(marker, WARN, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Object[])
+     */
+    public void warn(Marker marker, String format, Object[] argArray)
+    {
+        log(marker, WARN, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#warn(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
+     */
+    public void warn(Marker marker, String msg, Throwable t)
+    {
+        log(marker, WARN, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isErrorEnabled()
+     */
+    public boolean isErrorEnabled()
+    {
+        return _logger.isErrorEnabled();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(java.lang.String)
+     */
+    public void error(String msg)
+    {
+        log(null, ERROR, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object)
+     */
+    public void error(String format, Object arg)
+    {
+        log(null, ERROR, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void error(String format, Object arg1, Object arg2)
+    {
+        log(null, ERROR, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(java.lang.String, java.lang.Object[])
+     */
+    public void error(String format, Object[] argArray)
+    {
+        log(null, ERROR, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(java.lang.String, java.lang.Throwable)
+     */
+    public void error(String msg, Throwable t)
+    {
+        log(null, ERROR, msg, null, t);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#isErrorEnabled(org.slf4j.Marker)
+     */
+    public boolean isErrorEnabled(Marker marker)
+    {
+        return _logger.isErrorEnabled(marker);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String)
+     */
+    public void error(Marker marker, String msg)
+    {
+        log(marker, ERROR, msg, null, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object)
+     */
+    public void error(Marker marker, String format, Object arg)
+    {
+        log(marker, ERROR, format, new Object[]{arg}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object, java.lang.Object)
+     */
+    public void error(Marker marker, String format, Object arg1, Object arg2)
+    {
+        log(marker, ERROR, format, new Object[]{arg1,arg2}, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Object[])
+     */
+    public void error(Marker marker, String format, Object[] argArray)
+    {
+        log(marker, ERROR, format, argArray, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.slf4j.Logger#error(org.slf4j.Marker, java.lang.String, java.lang.Throwable)
+     */
+    public void error(Marker marker, String msg, Throwable t)
+    {
+        log(marker, ERROR, msg, null, t);
+    }
+
+    @Override
+    public String toString()
+    {
+        return _logger.toString();
+    }
+
+    private void log(Marker marker, int level, String msg, Object[] argArray, Throwable t)
+    {
+        if (argArray == null)
+        {
+            // Simple SLF4J Message (no args)
+            _logger.log(marker, FQCN, level, msg, null, t);
+        }
+        else
+        {
+            int loggerLevel = _logger.isTraceEnabled() ? TRACE :
+                    _logger.isDebugEnabled() ? DEBUG :
+                            _logger.isInfoEnabled() ? INFO :
+                                    _logger.isWarnEnabled() ? WARN : ERROR;
+            if (loggerLevel <= level)
+            {
+                // Don't assume downstream handles argArray properly.
+                // Do it the SLF4J way here to eliminate that as a bug.
+                FormattingTuple ft = MessageFormatter.arrayFormat(msg, argArray);
+                _logger.log(marker, FQCN, level, ft.getMessage(), null, t);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/Log.java b/src/java/org/eclipse/jetty/util/log/Log.java
new file mode 100644
index 0000000..a35c18c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/Log.java
@@ -0,0 +1,462 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+
+/**
+ * Logging.
+ * This class provides a static logging interface.  If an instance of the
+ * org.slf4j.Logger class is found on the classpath, the static log methods
+ * are directed to a slf4j logger for "org.eclipse.log".   Otherwise the logs
+ * are directed to stderr.
+ * <p>
+ * The "org.eclipse.jetty.util.log.class" system property can be used
+ * to select a specific logging implementation.
+ * <p>
+ * If the system property org.eclipse.jetty.util.log.IGNORED is set,
+ * then ignored exceptions are logged in detail.
+ *
+ * @see StdErrLog
+ * @see Slf4jLog
+ */
+public class Log
+{
+    public final static String EXCEPTION= "EXCEPTION ";
+    public final static String IGNORED= "IGNORED ";
+
+    /**
+     * Logging Configuration Properties
+     */
+    protected static Properties __props;
+    /**
+     * The {@link Logger} implementation class name
+     */
+    public static String __logClass;
+    /**
+     * Legacy flag indicating if {@link Log#ignore(Throwable)} methods produce any output in the {@link Logger}s
+     */
+    public static boolean __ignored;
+
+    /**
+     * Hold loggers only.
+     */
+    private final static ConcurrentMap<String, Logger> __loggers = new ConcurrentHashMap<String, Logger>();
+
+
+    static
+    {
+        /* Instantiate a default configuration properties (empty)
+         */
+        __props = new Properties();
+
+        AccessController.doPrivileged(new PrivilegedAction<Object>()
+        {
+            public Object run()
+            {
+                /* First see if the jetty-logging.properties object exists in the classpath.
+                 * This is an optional feature used by embedded mode use, and test cases to allow for early
+                 * configuration of the Log class in situations where access to the System.properties are
+                 * either too late or just impossible.
+                 */
+                URL testProps = Loader.getResource(Log.class,"jetty-logging.properties",true);
+                if (testProps != null)
+                {
+                    InputStream in = null;
+                    try
+                    {
+                        in = testProps.openStream();
+                        __props.load(in);
+                    }
+                    catch (IOException e)
+                    {
+                        System.err.println("Unable to load " + testProps);
+                        e.printStackTrace(System.err);
+                    }
+                    finally
+                    {
+                        IO.close(in);
+                    }
+                }
+
+                /* Now load the System.properties as-is into the __props, these values will override
+                 * any key conflicts in __props.
+                 */
+                @SuppressWarnings("unchecked")
+                Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames();
+                while (systemKeyEnum.hasMoreElements())
+                {
+                    String key = systemKeyEnum.nextElement();
+                    String val = System.getProperty(key);
+                    //protect against application code insertion of non-String values (returned as null)
+                    if (val != null)
+                        __props.setProperty(key,val);
+                }
+
+                /* Now use the configuration properties to configure the Log statics
+                 */
+                __logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog");
+                __ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false"));
+                return null;
+            }
+        });
+    }
+
+    private static Logger LOG;
+    private static boolean __initialized;
+
+    public static boolean initialized()
+    {
+        if (LOG != null)
+        {
+            return true;
+        }
+
+        synchronized (Log.class)
+        {
+            if (__initialized)
+            {
+                return LOG != null;
+            }
+            __initialized = true;
+        }
+
+        try
+        {
+            Class<?> log_class = Loader.loadClass(Log.class, __logClass);
+            if (LOG == null || !LOG.getClass().equals(log_class))
+            {
+                LOG = (Logger)log_class.newInstance();
+                LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+            }
+        }
+        catch(Throwable e)
+        {
+            // Unable to load specified Logger implementation, default to standard logging.
+            initStandardLogging(e);
+        }
+
+        return LOG != null;
+    }
+
+    private static void initStandardLogging(Throwable e)
+    {
+        Class<?> log_class;
+        if(e != null && __ignored)
+        {
+            e.printStackTrace();
+        }
+
+        if (LOG == null)
+        {
+            log_class = StdErrLog.class;
+            LOG = new StdErrLog();
+            LOG.debug("Logging to {} via {}", LOG, log_class.getName());
+        }
+    }
+
+    public static void setLog(Logger log)
+    {
+        Log.LOG = log;
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static Logger getLog()
+    {
+        initialized();
+        return LOG;
+    }
+
+    /**
+     * Get the root logger.
+     * @return the root logger
+     */
+    public static Logger getRootLogger() {
+        initialized();
+        return LOG;
+    }
+
+    static boolean isIgnored()
+    {
+        return __ignored;
+    }
+
+    /**
+     * Set Log to parent Logger.
+     * <p>
+     * If there is a different Log class available from a parent classloader,
+     * call {@link #getLogger(String)} on it and construct a {@link LoggerLog} instance
+     * as this Log's Logger, so that logging is delegated to the parent Log.
+     * <p>
+     * This should be used if a webapp is using Log, but wishes the logging to be
+     * directed to the containers log.
+     * <p>
+     * If there is not parent Log, then this call is equivalent to<pre>
+     *   Log.setLog(Log.getLogger(name));
+     * </pre>
+     * @param name Logger name
+     */
+    public static void setLogToParent(String name)
+    {
+        ClassLoader loader = Log.class.getClassLoader();
+        if (loader!=null && loader.getParent()!=null)
+        {
+            try
+            {
+                Class<?> uberlog = loader.getParent().loadClass("org.eclipse.jetty.util.log.Log");
+                Method getLogger = uberlog.getMethod("getLogger", new Class[]{String.class});
+                Object logger = getLogger.invoke(null,name);
+                setLog(new LoggerLog(logger));
+            }
+            catch (Exception e)
+            {
+                e.printStackTrace();
+            }
+        }
+        else
+        {
+            setLog(getLogger(name));
+        }
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void debug(Throwable th)
+    {
+        if (!isDebugEnabled())
+            return;
+        LOG.debug(EXCEPTION, th);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void debug(String msg)
+    {
+        if (!initialized())
+            return;
+        LOG.debug(msg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void debug(String msg, Object arg)
+    {
+        if (!initialized())
+            return;
+        LOG.debug(msg, arg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void debug(String msg, Object arg0, Object arg1)
+    {
+        if (!initialized())
+            return;
+        LOG.debug(msg, arg0, arg1);
+    }
+
+    /**
+     * Ignore an exception unless trace is enabled.
+     * This works around the problem that log4j does not support the trace level.
+     * @param thrown the Throwable to ignore
+     */
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void ignore(Throwable thrown)
+    {
+        if (!initialized())
+            return;
+        LOG.ignore(thrown);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void info(String msg)
+    {
+        if (!initialized())
+            return;
+        LOG.info(msg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void info(String msg, Object arg)
+    {
+        if (!initialized())
+            return;
+        LOG.info(msg, arg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void info(String msg, Object arg0, Object arg1)
+    {
+        if (!initialized())
+            return;
+        LOG.info(msg, arg0, arg1);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static boolean isDebugEnabled()
+    {
+        if (!initialized())
+            return false;
+        return LOG.isDebugEnabled();
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void warn(String msg)
+    {
+        if (!initialized())
+            return;
+        LOG.warn(msg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void warn(String msg, Object arg)
+    {
+        if (!initialized())
+            return;
+        LOG.warn(msg, arg);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void warn(String msg, Object arg0, Object arg1)
+    {
+        if (!initialized())
+            return;
+        LOG.warn(msg, arg0, arg1);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void warn(String msg, Throwable th)
+    {
+        if (!initialized())
+            return;
+        LOG.warn(msg, th);
+    }
+
+    /**
+     * @deprecated anonymous logging is deprecated, use a named {@link Logger} obtained from {@link #getLogger(String)}
+     */
+    @Deprecated
+    public static void warn(Throwable th)
+    {
+        if (!initialized())
+            return;
+        LOG.warn(EXCEPTION, th);
+    }
+
+    /**
+     * Obtain a named Logger based on the fully qualified class name.
+     *
+     * @param clazz
+     *            the class to base the Logger name off of
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(Class<?> clazz)
+    {
+        return getLogger(clazz.getName());
+    }
+
+    /**
+     * Obtain a named Logger or the default Logger if null is passed.
+     * @param name the Logger name
+     * @return the Logger with the given name
+     */
+    public static Logger getLogger(String name)
+    {
+        if (!initialized())
+            return null;
+
+        if(name==null)
+            return LOG;
+
+        Logger logger = __loggers.get(name);
+        if(logger==null)
+            logger = LOG.getLogger(name);
+
+        return logger;
+    }
+
+    static ConcurrentMap<String, Logger> getMutableLoggers()
+    {
+        return __loggers;
+    }
+    
+    /**
+     * Get a map of all configured {@link Logger} instances.
+     *
+     * @return a map of all configured {@link Logger} instances
+     */
+    public static Map<String, Logger> getLoggers()
+    {
+        return Collections.unmodifiableMap(__loggers);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/Logger.java b/src/java/org/eclipse/jetty/util/log/Logger.java
new file mode 100644
index 0000000..83c499f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/Logger.java
@@ -0,0 +1,113 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+/**
+ * A simple logging facade that is intended simply to capture the style of logging as used by Jetty.
+ */
+public interface Logger
+{
+    /**
+     * @return the name of this logger
+     */
+    public String getName();
+
+    /**
+     * Formats and logs at warn level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void warn(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at warn level
+     * @param thrown the Throwable to log
+     */
+    public void warn(Throwable thrown);
+
+    /**
+     * Logs the given message at warn level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void warn(String msg, Throwable thrown);
+
+    /**
+     * Formats and logs at info level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void info(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at info level
+     * @param thrown the Throwable to log
+     */
+    public void info(Throwable thrown);
+
+    /**
+     * Logs the given message at info level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void info(String msg, Throwable thrown);
+
+    /**
+     * @return whether the debug level is enabled
+     */
+    public boolean isDebugEnabled();
+
+    /**
+     * Mutator used to turn debug on programmatically.
+     * @param enabled whether to enable the debug level
+     */
+    public void setDebugEnabled(boolean enabled);
+
+    /**
+     * Formats and logs at debug level.
+     * @param msg the formatting string
+     * @param args the optional arguments
+     */
+    public void debug(String msg, Object... args);
+
+    /**
+     * Logs the given Throwable information at debug level
+     * @param thrown the Throwable to log
+     */
+    public void debug(Throwable thrown);
+
+    /**
+     * Logs the given message at debug level, with Throwable information.
+     * @param msg the message to log
+     * @param thrown the Throwable to log
+     */
+    public void debug(String msg, Throwable thrown);
+
+    /**
+     * @param name the name of the logger
+     * @return a logger with the given name
+     */
+    public Logger getLogger(String name);
+    
+    /**
+     * Ignore an exception.
+     * <p>This should be used rather than an empty catch block.
+     */
+    public void ignore(Throwable ignored); 
+}
diff --git a/src/java/org/eclipse/jetty/util/log/LoggerLog.java b/src/java/org/eclipse/jetty/util/log/LoggerLog.java
new file mode 100644
index 0000000..f6fc052
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/LoggerLog.java
@@ -0,0 +1,213 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.lang.reflect.Method;
+
+/**
+ *
+ */
+public class LoggerLog extends AbstractLogger
+{
+    private final Object _logger;
+    private final Method _debugMT;
+    private final Method _debugMAA;
+    private final Method _infoMT;
+    private final Method _infoMAA;
+    private final Method _warnMT;
+    private final Method _warnMAA;
+    private final Method _setDebugEnabledE;
+    private final Method _getLoggerN;
+    private final Method _getName;
+    private volatile boolean _debug;
+
+    public LoggerLog(Object logger)
+    {
+        try
+        {
+            _logger = logger;
+            Class<?> lc = logger.getClass();
+            _debugMT = lc.getMethod("debug", new Class[]{String.class, Throwable.class});
+            _debugMAA = lc.getMethod("debug", new Class[]{String.class, Object[].class});
+            _infoMT = lc.getMethod("info", new Class[]{String.class, Throwable.class});
+            _infoMAA = lc.getMethod("info", new Class[]{String.class, Object[].class});
+            _warnMT = lc.getMethod("warn", new Class[]{String.class, Throwable.class});
+            _warnMAA = lc.getMethod("warn", new Class[]{String.class, Object[].class});
+            Method _isDebugEnabled = lc.getMethod("isDebugEnabled");
+            _setDebugEnabledE = lc.getMethod("setDebugEnabled", new Class[]{Boolean.TYPE});
+            _getLoggerN = lc.getMethod("getLogger", new Class[]{String.class});
+            _getName = lc.getMethod("getName");
+
+            _debug = (Boolean)_isDebugEnabled.invoke(_logger);
+        }
+        catch(Exception x)
+        {
+            throw new IllegalStateException(x);
+        }
+    }
+
+    public String getName()
+    {
+        try
+        {
+            return (String)_getName.invoke(_logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        try
+        {
+            _warnMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        try
+        {
+            _warnMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        try
+        {
+            _infoMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        try
+        {
+            _infoMT.invoke(_logger, msg, thrown);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _debug;
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        try
+        {
+            _setDebugEnabledE.invoke(_logger, enabled);
+            _debug = enabled;
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMAA.invoke(_logger, args);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable th)
+    {
+        if (!_debug)
+            return;
+
+        try
+        {
+            _debugMT.invoke(_logger, msg, th);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        try
+        {
+            Object logger=_getLoggerN.invoke(_logger, fullname);
+            return new LoggerLog(logger);
+        }
+        catch (Exception e)
+        {
+            e.printStackTrace();
+            return this;
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/Slf4jLog.java b/src/java/org/eclipse/jetty/util/log/Slf4jLog.java
new file mode 100644
index 0000000..c5f739d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/Slf4jLog.java
@@ -0,0 +1,133 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+
+
+/**
+ * Slf4jLog Logger
+ */
+public class Slf4jLog extends AbstractLogger
+{
+    private final org.slf4j.Logger _logger;
+
+    public Slf4jLog() throws Exception
+    {
+        this("org.eclipse.jetty.util.log");
+    }
+
+    public Slf4jLog(String name)
+    {
+        //NOTE: if only an slf4j-api jar is on the classpath, slf4j will use a NOPLogger
+        org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name );
+        
+        // Fix LocationAwareLogger use to indicate FQCN of this class - 
+        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670
+        if (logger instanceof org.slf4j.spi.LocationAwareLogger)
+        {
+            _logger = new JettyAwareLogger((org.slf4j.spi.LocationAwareLogger)logger);
+        }
+        else
+        {
+            _logger = logger;
+        }
+    }
+
+    public String getName()
+    {
+        return _logger.getName();
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        _logger.warn(msg, args);
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("", thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        _logger.warn(msg, thrown);
+    }
+
+    public void info(String msg, Object... args)
+    {
+        _logger.info(msg, args);
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("", thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        _logger.info(msg, thrown);
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        _logger.debug(msg, args);
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("", thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        _logger.debug(msg, thrown);
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return _logger.isDebugEnabled();
+    }
+
+    public void setDebugEnabled(boolean enabled)
+    {
+        warn("setDebugEnabled not implemented",null,null);
+    }
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        return new Slf4jLog(fullname);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (Log.isIgnored())
+        {
+            warn(Log.IGNORED, ignored);
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        return _logger.toString();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/StdErrLog.java b/src/java/org/eclipse/jetty/util/log/StdErrLog.java
new file mode 100644
index 0000000..8373c07
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/StdErrLog.java
@@ -0,0 +1,630 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log;
+
+import java.io.PrintStream;
+import java.security.AccessControlException;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.DateCache;
+
+/**
+ * StdErr Logging. This implementation of the Logging facade sends all logs to StdErr with minimal formatting.
+ * <p>
+ * If the system property "org.eclipse.jetty.LEVEL" is set to one of the following (ALL, DEBUG, INFO, WARN), then set
+ * the eclipse jetty root level logger level to that specified level. (Default level is INFO)
+ * <p>
+ * If the system property "org.eclipse.jetty.util.log.SOURCE" is set, then the source method/file of a log is logged.
+ * For named debuggers, the system property name+".SOURCE" is checked, eg "org.eclipse.jetty.util.log.stderr.SOURCE". 
+ * If it is not not set, then "org.eclipse.jetty.util.log.SOURCE" is used as the default.
+ * <p>
+ * If the system property "org.eclipse.jetty.util.log.stderr.LONG" is set, then the full, unabbreviated name of the logger is
+ * used for logging.
+ */
+public class StdErrLog extends AbstractLogger
+{
+    private static final String EOL = System.getProperty("line.separator");
+    private static DateCache _dateCache;
+    private static final Properties __props = new Properties();
+
+    private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
+            Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
+    private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
+
+    static
+    {
+        __props.putAll(Log.__props);
+        
+        String deprecatedProperties[] =
+        { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
+
+        // Toss a message to users about deprecated system properties
+        for (String deprecatedProp : deprecatedProperties)
+        {
+            if (System.getProperty(deprecatedProp) != null)
+            {
+                System.err.printf("System Property [%s] has been deprecated! (Use org.eclipse.jetty.LEVEL=DEBUG instead)%n",deprecatedProp);
+            }
+        }
+
+        try
+        {
+            _dateCache = new DateCache("yyyy-MM-dd HH:mm:ss");
+        }
+        catch (Exception x)
+        {
+            x.printStackTrace(System.err);
+        }
+    }
+
+    public static final int LEVEL_ALL = 0;
+    public static final int LEVEL_DEBUG = 1;
+    public static final int LEVEL_INFO = 2;
+    public static final int LEVEL_WARN = 3;
+
+    private int _level = LEVEL_INFO;
+    // Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
+    private int _configuredLevel;
+    private PrintStream _stderr = null;
+    private boolean _source = __source;
+    // Print the long form names, otherwise use abbreviated
+    private boolean _printLongNames = __long;
+    // The full log name, as provided by the system.
+    private final String _name;
+    // The abbreviated log name (used by default, unless _long is specified)
+    private final String _abbrevname;
+    private boolean _hideStacks = false;
+
+    public StdErrLog()
+    {
+        this(null);
+    }
+
+    public StdErrLog(String name)
+    {
+        this(name,__props);
+    }
+
+    public StdErrLog(String name, Properties props)
+    {
+        if (props!=null && props!=__props)
+            __props.putAll(props);
+        this._name = name == null?"":name;
+        this._abbrevname = condensePackageString(this._name);
+        this._level = getLoggingLevel(props,this._name);
+        this._configuredLevel = this._level;
+
+        try
+        {
+            _source = Boolean.parseBoolean(props.getProperty(_name + ".SOURCE",Boolean.toString(_source)));
+        }
+        catch (AccessControlException ace)
+        {
+            _source = __source;
+        }
+    }
+
+    /**
+     * Get the Logging Level for the provided log name. Using the FQCN first, then each package segment from longest to
+     * shortest.
+     *
+     * @param props
+     *            the properties to check
+     * @param name
+     *            the name to get log for
+     * @return the logging level
+     */
+    public static int getLoggingLevel(Properties props, final String name)
+    {
+        // Calculate the level this named logger should operate under.
+        // Checking with FQCN first, then each package segment from longest to shortest.
+        String nameSegment = name;
+
+        while ((nameSegment != null) && (nameSegment.length() > 0))
+        {
+            String levelStr = props.getProperty(nameSegment + ".LEVEL");
+            // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
+            int level = getLevelId(nameSegment + ".LEVEL",levelStr);
+            if (level != (-1))
+            {
+                return level;
+            }
+
+            // Trim and try again.
+            int idx = nameSegment.lastIndexOf('.');
+            if (idx >= 0)
+            {
+                nameSegment = nameSegment.substring(0,idx);
+            }
+            else
+            {
+                nameSegment = null;
+            }
+        }
+
+        // Default Logging Level
+        return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
+    }
+
+    protected static int getLevelId(String levelSegment, String levelName)
+    {
+        if (levelName == null)
+        {
+            return -1;
+        }
+        String levelStr = levelName.trim();
+        if ("ALL".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_ALL;
+        }
+        else if ("DEBUG".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_DEBUG;
+        }
+        else if ("INFO".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_INFO;
+        }
+        else if ("WARN".equalsIgnoreCase(levelStr))
+        {
+            return LEVEL_WARN;
+        }
+
+        System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values.");
+        return -1;
+    }
+
+    /**
+     * Condenses a classname by stripping down the package name to just the first character of each package name
+     * segment.Configured
+     * <p>
+     *
+     * <pre>
+     * Examples:
+     * "org.eclipse.jetty.test.FooTest"           = "oejt.FooTest"
+     * "org.eclipse.jetty.server.logging.LogTest" = "orjsl.LogTest"
+     * </pre>
+     *
+     * @param classname
+     *            the fully qualified class name
+     * @return the condensed name
+     */
+    protected static String condensePackageString(String classname)
+    {
+        String parts[] = classname.split("\\.");
+        StringBuilder dense = new StringBuilder();
+        for (int i = 0; i < (parts.length - 1); i++)
+        {
+            dense.append(parts[i].charAt(0));
+        }
+        if (dense.length() > 0)
+        {
+            dense.append('.');
+        }
+        dense.append(parts[parts.length - 1]);
+        return dense.toString();
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public void setPrintLongNames(boolean printLongNames)
+    {
+        this._printLongNames = printLongNames;
+    }
+
+    public boolean isPrintLongNames()
+    {
+        return this._printLongNames;
+    }
+
+    public boolean isHideStacks()
+    {
+        return _hideStacks;
+    }
+
+    public void setHideStacks(boolean hideStacks)
+    {
+        _hideStacks = hideStacks;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Is the source of a log, logged
+     *
+     * @return true if the class, method, file and line number of a log is logged.
+     */
+    public boolean isSource()
+    {
+        return _source;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if a log source is logged.
+     *
+     * @param source
+     *            true if the class, method, file and line number of a log is logged.
+     */
+    public void setSource(boolean source)
+    {
+        _source = source;
+    }
+
+    public void warn(String msg, Object... args)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void warn(Throwable thrown)
+    {
+        warn("",thrown);
+    }
+
+    public void warn(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_WARN)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":WARN:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(String msg, Object... args)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void info(Throwable thrown)
+    {
+        info("",thrown);
+    }
+
+    public void info(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_INFO)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":INFO:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public boolean isDebugEnabled()
+    {
+        return (_level <= LEVEL_DEBUG);
+    }
+
+    /**
+     * Legacy interface where a programmatic configuration of the logger level
+     * is done as a wholesale approach.
+     */
+    public void setDebugEnabled(boolean enabled)
+    {
+        if (enabled)
+        {
+            this._level = LEVEL_DEBUG;
+
+            for (Logger log : Log.getLoggers().values())
+            {                
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(LEVEL_DEBUG);
+            }
+        }
+        else
+        {
+            this._level = this._configuredLevel;
+            
+            for (Logger log : Log.getLoggers().values())
+            {
+                if (log.getName().startsWith(getName()) && log instanceof StdErrLog)
+                    ((StdErrLog)log).setLevel(((StdErrLog)log)._configuredLevel);
+            }
+        }
+    }
+
+    public int getLevel()
+    {
+        return _level;
+    }
+
+    /**
+     * Set the level for this logger.
+     * <p>
+     * Available values ({@link StdErrLog#LEVEL_ALL}, {@link StdErrLog#LEVEL_DEBUG}, {@link StdErrLog#LEVEL_INFO},
+     * {@link StdErrLog#LEVEL_WARN})
+     *
+     * @param level
+     *            the level to set the logger to
+     */
+    public void setLevel(int level)
+    {
+        this._level = level;
+    }
+
+    public void setStdErrStream(PrintStream stream)
+    {
+        this._stderr = stream==System.err?null:stream;
+    }
+
+    public void debug(String msg, Object... args)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,args);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    public void debug(Throwable thrown)
+    {
+        debug("",thrown);
+    }
+
+    public void debug(String msg, Throwable thrown)
+    {
+        if (_level <= LEVEL_DEBUG)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":DBUG:",msg,thrown);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Object... args)
+    {
+        String d = _dateCache.now();
+        int ms = _dateCache.lastMs();
+        tag(buffer,d,ms,level);
+        format(buffer,msg,args);
+    }
+
+    private void format(StringBuilder buffer, String level, String msg, Throwable thrown)
+    {
+        format(buffer,level,msg);
+        if (isHideStacks())
+        {
+            format(buffer,String.valueOf(thrown));
+        }
+        else
+        {
+            format(buffer,thrown);
+        }
+    }
+
+    private void tag(StringBuilder buffer, String d, int ms, String tag)
+    {
+        buffer.setLength(0);
+        buffer.append(d);
+        if (ms > 99)
+        {
+            buffer.append('.');
+        }
+        else if (ms > 9)
+        {
+            buffer.append(".0");
+        }
+        else
+        {
+            buffer.append(".00");
+        }
+        buffer.append(ms).append(tag);
+        if (_printLongNames)
+        {
+            buffer.append(_name);
+        }
+        else
+        {
+            buffer.append(_abbrevname);
+        }
+        buffer.append(':');
+        if (_source)
+        {
+            Throwable source = new Throwable();
+            StackTraceElement[] frames = source.getStackTrace();
+            for (int i = 0; i < frames.length; i++)
+            {
+                final StackTraceElement frame = frames[i];
+                String clazz = frame.getClassName();
+                if (clazz.equals(StdErrLog.class.getName()) || clazz.equals(Log.class.getName()))
+                {
+                    continue;
+                }
+                if (!_printLongNames && clazz.startsWith("org.eclipse.jetty."))
+                {
+                    buffer.append(condensePackageString(clazz));
+                }
+                else
+                {
+                    buffer.append(clazz);
+                }
+                buffer.append('#').append(frame.getMethodName());
+                if (frame.getFileName() != null)
+                {
+                    buffer.append('(').append(frame.getFileName()).append(':').append(frame.getLineNumber()).append(')');
+                }
+                buffer.append(':');
+                break;
+            }
+        }
+    }
+
+    private void format(StringBuilder builder, String msg, Object... args)
+    {
+        if (msg == null)
+        {
+            msg = "";
+            for (int i = 0; i < args.length; i++)
+            {
+                msg += "{} ";
+            }
+        }
+        String braces = "{}";
+        int start = 0;
+        for (Object arg : args)
+        {
+            int bracesIndex = msg.indexOf(braces,start);
+            if (bracesIndex < 0)
+            {
+                escape(builder,msg.substring(start));
+                builder.append(" ");
+                builder.append(arg);
+                start = msg.length();
+            }
+            else
+            {
+                escape(builder,msg.substring(start,bracesIndex));
+                builder.append(String.valueOf(arg));
+                start = bracesIndex + braces.length();
+            }
+        }
+        escape(builder,msg.substring(start));
+    }
+
+    private void escape(StringBuilder builder, String string)
+    {
+        for (int i = 0; i < string.length(); ++i)
+        {
+            char c = string.charAt(i);
+            if (Character.isISOControl(c))
+            {
+                if (c == '\n')
+                {
+                    builder.append('|');
+                }
+                else if (c == '\r')
+                {
+                    builder.append('<');
+                }
+                else
+                {
+                    builder.append('?');
+                }
+            }
+            else
+            {
+                builder.append(c);
+            }
+        }
+    }
+
+    private void format(StringBuilder buffer, Throwable thrown)
+    {
+        if (thrown == null)
+        {
+            buffer.append("null");
+        }
+        else
+        {
+            buffer.append(EOL);
+            format(buffer,thrown.toString());
+            StackTraceElement[] elements = thrown.getStackTrace();
+            for (int i = 0; elements != null && i < elements.length; i++)
+            {
+                buffer.append(EOL).append("\tat ");
+                format(buffer,elements[i].toString());
+            }
+
+            Throwable cause = thrown.getCause();
+            if (cause != null && cause != thrown)
+            {
+                buffer.append(EOL).append("Caused by: ");
+                format(buffer,cause);
+            }
+        }
+    }
+
+
+    /**
+     * Create a Child Logger of this Logger.
+     */
+    protected Logger newLogger(String fullname)
+    {
+        StdErrLog logger = new StdErrLog(fullname);
+        // Preserve configuration for new loggers configuration
+        logger.setPrintLongNames(_printLongNames);
+        // Let Level come from configured Properties instead - sel.setLevel(_level);
+        logger.setSource(_source);
+        logger._stderr = this._stderr;
+        
+        // Force the child to have any programmatic configuration
+        if (_level!=_configuredLevel)
+            logger._level=_level;
+
+        return logger;
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder s = new StringBuilder();
+        s.append("StdErrLog:");
+        s.append(_name);
+        s.append(":LEVEL=");
+        switch (_level)
+        {
+            case LEVEL_ALL:
+                s.append("ALL");
+                break;
+            case LEVEL_DEBUG:
+                s.append("DEBUG");
+                break;
+            case LEVEL_INFO:
+                s.append("INFO");
+                break;
+            case LEVEL_WARN:
+                s.append("WARN");
+                break;
+            default:
+                s.append("?");
+                break;
+        }
+        return s.toString();
+    }
+
+    public static void setProperties(Properties props)
+    {
+        __props.clear();
+        __props.putAll(props);
+    }
+
+    public void ignore(Throwable ignored)
+    {
+        if (_level <= LEVEL_ALL)
+        {
+            StringBuilder buffer = new StringBuilder(64);
+            format(buffer,":IGNORED:","",ignored);
+            (_stderr==null?System.err:_stderr).println(buffer);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/log/jmx/LogMBean.java b/src/java/org/eclipse/jetty/util/log/jmx/LogMBean.java
new file mode 100644
index 0000000..9581a24
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/log/jmx/LogMBean.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.log.jmx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.jmx.ObjectMBean;
+import org.eclipse.jetty.util.log.Log;
+
+/* ------------------------------------------------------------ */
+/**
+ */
+public class LogMBean extends ObjectMBean
+{
+
+    public LogMBean(Object managedObject)
+    {
+        super(managedObject);
+    }
+
+    public List<String> getLoggers()
+    {
+        List<String> keySet = new ArrayList<String>(Log.getLoggers().keySet());
+        return keySet;
+    }
+
+    public boolean isDebugEnabled(String logger)
+    {
+        return Log.getLogger(logger).isDebugEnabled();
+    }
+
+    public void setDebugEnabled(String logger, Boolean enabled)
+    {
+        Log.getLogger(logger).setDebugEnabled(enabled);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
new file mode 100644
index 0000000..0f6caf9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/AWTLeakPreventer.java
@@ -0,0 +1,47 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.awt.Toolkit;
+
+/**
+ * AWTLeakPreventer
+ *
+ * See https://issues.jboss.org/browse/AS7-3733
+ * 
+ * The java.awt.Toolkit class has a static field that is the default toolkit. 
+ * Creating the default toolkit causes the creation of an EventQueue, which has a 
+ * classloader field initialized by the thread context class loader. 
+ *
+ */
+public class AWTLeakPreventer extends AbstractLeakPreventer
+{
+   
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for java.awt.EventQueue using "+loader);
+        Toolkit.getDefaultToolkit();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java
new file mode 100644
index 0000000..2322a76
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java
@@ -0,0 +1,62 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * AbstractLeakPreventer
+ *
+ * Abstract base class for code that seeks to avoid pinning of webapp classloaders by using the jetty classloader to
+ * proactively call the code that pins them (generally pinned as static data members, or as static
+ * data members that are daemon threads (which use the context classloader)).
+ * 
+ * Instances of subclasses of this class should be set with Server.addBean(), which will
+ * ensure that they are called when the Server instance starts up, which will have the jetty
+ * classloader in scope.
+ *
+ */
+public abstract class AbstractLeakPreventer extends AbstractLifeCycle
+{
+    protected static final Logger LOG = Log.getLogger(AbstractLeakPreventer.class);
+    
+    /* ------------------------------------------------------------ */
+    abstract public void prevent(ClassLoader loader);
+    
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        ClassLoader loader = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            prevent(getClass().getClassLoader());
+            super.doStart();
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader(loader);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
new file mode 100644
index 0000000..0cfd3c9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/AppContextLeakPreventer.java
@@ -0,0 +1,41 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.imageio.ImageIO;
+
+/**
+ * AppContextLeakPreventer
+ *
+ * Cause the classloader that is pinned by AppContext.getAppContext() to be 
+ * a container or system classloader, not a webapp classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class AppContextLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning classloader for AppContext.getContext() with "+loader);
+        ImageIO.getUseCache();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java
new file mode 100644
index 0000000..5fee365
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/DOMLeakPreventer.java
@@ -0,0 +1,56 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+/**
+ * DOMLeakPreventer
+ *
+ * See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6916498
+ * 
+ * Prevent the RuntimeException that is a static member of AbstractDOMParser
+ * from pinning a webapp classloader by causing it to be set here by a non-webapp classloader.
+ * 
+ * Note that according to the bug report, a heap dump may not identify the GCRoot, making 
+ * it difficult to identify the cause of the leak.
+ *
+ */
+public class DOMLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try 
+        {
+            factory.newDocumentBuilder();
+        } 
+        catch (Exception e) 
+        {
+            LOG.warn(e);
+        }
+
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
new file mode 100644
index 0000000..d229ba7
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/DriverManagerLeakPreventer.java
@@ -0,0 +1,42 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.preventers;
+
+import java.sql.DriverManager;
+
+
+/**
+ * DriverManagerLeakPreventer
+ *
+ * Cause DriverManager.getCallerClassLoader() to be called, which will pin the classloader.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ */
+public class DriverManagerLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        LOG.debug("Pinning DriverManager classloader with "+loader);
+        DriverManager.getDrivers();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
new file mode 100644
index 0000000..6ea4de2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java
@@ -0,0 +1,64 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.lang.reflect.Method;
+
+/**
+ * GCThreadLeakPreventer
+ *
+ * Prevents a call to sun.misc.GC.requestLatency pinning a webapp classloader
+ * by calling it with a non-webapp classloader. The problem appears to be that
+ * when this method is called, a daemon thread is created which takes the 
+ * context classloader. A known caller of this method is the RMI impl. See
+ * http://stackoverflow.com/questions/6626680/does-java-garbage-collection-log-entry-full-gc-system-mean-some-class-called
+ * 
+ * This preventer will start the thread with the longest possible interval, although
+ * subsequent calls can vary that. Recommend to only use this class if you're doing
+ * RMI.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention.
+ *
+ */
+public class GCThreadLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class clazz = Class.forName("sun.misc.GC");
+            Method requestLatency = clazz.getMethod("requestLatency", new Class[] {long.class});
+            requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE-1));
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java
new file mode 100644
index 0000000..3a2ad82
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/Java2DLeakPreventer.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * Java2DLeakPreventer
+ *
+ * Prevent pinning of webapp classloader by pre-loading sun.java2d.Disposer class
+ * before webapp classloaders are created.
+ * 
+ * See https://issues.apache.org/bugzilla/show_bug.cgi?id=51687
+ *
+ */
+public class Java2DLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("sun.java2d.Disposer", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java
new file mode 100644
index 0000000..5c497d1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/LDAPLeakPreventer.java
@@ -0,0 +1,51 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LDAPLeakPreventer
+ *
+ * If com.sun.jndi.LdapPoolManager class is loaded and the system property
+ * com.sun.jndi.ldap.connect.pool.timeout is set to a nonzero value, a daemon
+ * thread is started which can pin a webapp classloader if it is the first to
+ * load the LdapPoolManager.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ *
+ */
+public class LDAPLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("com.sun.jndi.LdapPoolManager", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java
new file mode 100644
index 0000000..c1d9fe2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/LoginConfigurationLeakPreventer.java
@@ -0,0 +1,49 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+/**
+ * LoginConfigurationLeakPreventer
+ *
+ * The javax.security.auth.login.Configuration class keeps a static reference to the 
+ * thread context classloader. We prevent a webapp context classloader being used for
+ * that by invoking the classloading here.
+ * 
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class LoginConfigurationLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        try
+        {
+            Class.forName("javax.security.auth.login.Configuration", true, loader);
+        }
+        catch (ClassNotFoundException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java b/src/java/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java
new file mode 100644
index 0000000..9976c56
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/preventers/SecurityProviderLeakPreventer.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.preventers;
+
+import java.security.Security;
+
+/**
+ * SecurityProviderLeakPreventer
+ *
+ * Some security providers, such as sun.security.pkcs11.SunPKCS11 start a deamon thread,
+ * which will use the thread context classloader. Load them here to ensure the classloader
+ * is not a webapp classloader.
+ *
+ * Inspired by Tomcat JreMemoryLeakPrevention
+ */
+public class SecurityProviderLeakPreventer extends AbstractLeakPreventer
+{
+    /* ------------------------------------------------------------ */
+    /** 
+     * @see org.eclipse.jetty.util.preventers.AbstractLeakPreventer#prevent(java.lang.ClassLoader)
+     */
+    @Override
+    public void prevent(ClassLoader loader)
+    {
+        Security.getProviders();
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/BadResource.java b/src/java/org/eclipse/jetty/util/resource/BadResource.java
new file mode 100644
index 0000000..fbb27f2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/BadResource.java
@@ -0,0 +1,139 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+
+
+/* ------------------------------------------------------------ */
+/** Bad Resource.
+ *
+ * A Resource that is returned for a bade URL.  Acts as a resource
+ * that does not exist and throws appropriate exceptions.
+ *
+ * 
+ */
+class BadResource extends URLResource
+{
+    /* ------------------------------------------------------------ */
+    private String _message=null;
+        
+    /* -------------------------------------------------------- */
+    BadResource(URL url,  String message)
+    {
+        super(url,null);
+        _message=message;
+    }
+    
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean exists()
+    {
+        return false;
+    }
+        
+    /* -------------------------------------------------------- */
+    @Override
+    public long lastModified()
+    {
+        return -1;
+    }
+
+    /* -------------------------------------------------------- */
+    @Override
+    public boolean isDirectory()
+    {
+        return false;
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public long length()
+    {
+        return -1;
+    }
+        
+        
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+    {
+        return null;
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        throw new FileNotFoundException(_message);
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public OutputStream getOutputStream()
+        throws java.io.IOException, SecurityException
+    {
+        throw new FileNotFoundException(_message);
+    }
+        
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException(_message);
+    }
+
+    /* --------------------------------------------------------- */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        throw new SecurityException(_message);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return super.toString()+"; BadResource="+_message;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/FileResource.java b/src/java/org/eclipse/jetty/util/resource/FileResource.java
new file mode 100644
index 0000000..322c13c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/FileResource.java
@@ -0,0 +1,400 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** File Resource.
+ *
+ * Handle resources of implied or explicit file type.
+ * This class can check for aliasing in the filesystem (eg case
+ * insensitivity).  By default this is turned on, or it can be controlled 
+ * by calling the static method @see FileResource#setCheckAliases(boolean)
+ * 
+ */
+public class FileResource extends URLResource
+{
+    private static final Logger LOG = Log.getLogger(FileResource.class);
+    private static boolean __checkAliases = true;
+
+    /* ------------------------------------------------------------ */
+    private File _file;
+    private transient URL _alias=null;
+    private transient boolean _aliasChecked=false;
+
+    /* ------------------------------------------------------------------------------- */
+    /** setCheckAliases.
+     * @param checkAliases True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
+     */
+    public static void setCheckAliases(boolean checkAliases)
+    {
+        __checkAliases=checkAliases;
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /** getCheckAliases.
+     * @return True of resource aliases are to be checked for (eg case insensitivity or 8.3 short names) and treated as not found.
+     */
+    public static boolean getCheckAliases()
+    {
+        return __checkAliases;
+    }
+    
+    /* -------------------------------------------------------- */
+    public FileResource(URL url)
+        throws IOException, URISyntaxException
+    {
+        super(url,null);
+
+        try
+        {
+            // Try standard API to convert URL to file.
+            _file =new File(new URI(url.toString()));
+        }
+        catch (URISyntaxException e) 
+        {
+            throw e;
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+            try
+            {
+                // Assume that File.toURL produced unencoded chars. So try
+                // encoding them.
+                String file_url="file:"+URIUtil.encodePath(url.toString().substring(5));           
+                URI uri = new URI(file_url);
+                if (uri.getAuthority()==null) 
+                    _file = new File(uri);
+                else
+                    _file = new File("//"+uri.getAuthority()+URIUtil.decodePath(url.getFile()));
+            }
+            catch (Exception e2)
+            {
+                LOG.ignore(e2);
+
+                // Still can't get the file.  Doh! try good old hack!
+                checkConnection();
+                Permission perm = _connection.getPermission();
+                _file = new File(perm==null?url.getFile():perm.getName());
+            }
+        }
+        if (_file.isDirectory())
+        {
+            if (!_urlString.endsWith("/"))
+                _urlString=_urlString+"/";
+        }
+        else
+        {
+            if (_urlString.endsWith("/"))
+                _urlString=_urlString.substring(0,_urlString.length()-1);
+        }
+
+    }
+
+    /* -------------------------------------------------------- */
+    FileResource(URL url, URLConnection connection, File file)
+    {
+        super(url,connection);
+        _file=file;
+        if (_file.isDirectory() && !_urlString.endsWith("/"))
+            _urlString=_urlString+"/";
+    }
+    
+    /* -------------------------------------------------------- */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        URLResource r=null;
+        String url=null;
+
+        path = org.eclipse.jetty.util.URIUtil.canonicalPath(path);
+       
+        if ("/".equals(path))
+            return this;
+        else if (!isDirectory())
+        {
+            r=(FileResource)super.addPath(path);
+            url=r._urlString;
+        }
+        else
+        {
+            if (path==null)
+                throw new MalformedURLException();   
+            
+            // treat all paths being added as relative
+            String rel=path;
+            if (path.startsWith("/"))
+                rel = path.substring(1);
+            
+            url=URIUtil.addPaths(_urlString,URIUtil.encodePath(rel));
+            r=(URLResource)Resource.newResource(url);
+        }
+        
+        // Check for encoding aliases
+        // The encoded path should be a suffix of the resource (give or take a directory / )
+        String encoded=URIUtil.encodePath(path);
+        int expected=r.toString().length()-encoded.length();
+        int index = r._urlString.lastIndexOf(encoded, expected);
+        if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory()))
+        {
+            if (r instanceof FileResource)
+            {
+                ((FileResource)r)._alias=((FileResource)r)._file.getCanonicalFile().toURI().toURL();
+                ((FileResource)r)._aliasChecked=true;
+            }
+        }                             
+        return r;
+    }
+   
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URL getAlias()
+    {
+        if (__checkAliases && !_aliasChecked)
+        {
+            try
+            {    
+                String abs=_file.getAbsolutePath();
+                String can=_file.getCanonicalPath();
+                
+                if (abs.length()!=can.length() || !abs.equals(can))
+                    _alias=Resource.toURL(new File(can));
+                
+                _aliasChecked=true;
+                
+                if (_alias!=null && LOG.isDebugEnabled())
+                {
+                    LOG.debug("ALIAS abs="+abs);
+                    LOG.debug("ALIAS can="+can);
+                }
+            }
+            catch(Exception e)
+            {
+                LOG.warn(Log.EXCEPTION,e);
+                return getURL();
+            }                
+        }
+        return _alias;
+    }
+    
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        return _file.exists();
+    }
+        
+    /* -------------------------------------------------------- */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        return _file.lastModified();
+    }
+
+    /* -------------------------------------------------------- */
+    /**
+     * Returns true if the respresenetd resource is a container/directory.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _file.isDirectory();
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        return _file.length();
+    }
+        
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _file.getAbsolutePath();
+    }
+        
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+    {
+        return _file;
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        return new FileInputStream(_file);
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Returns an output stream to the resource
+     */
+    @Override
+    public OutputStream getOutputStream()
+        throws java.io.IOException, SecurityException
+    {
+        return new FileOutputStream(_file);
+    }
+        
+    /* --------------------------------------------------------- */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        return _file.delete();
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        if( dest instanceof FileResource)
+            return _file.renameTo( ((FileResource)dest)._file);
+        else
+            return false;
+    }
+
+    /* --------------------------------------------------------- */
+    /**
+     * Returns a list of resources contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        String[] list =_file.list();
+        if (list==null)
+            return null;
+        for (int i=list.length;i-->0;)
+        {
+            if (new File(_file,list[i]).isDirectory() &&
+                !list[i].endsWith("/"))
+                list[i]+="/";
+        }
+        return list;
+    }
+         
+    /* ------------------------------------------------------------ */
+    /** Encode according to this resource type.
+     * File URIs are encoded.
+     * @param uri URI to encode.
+     * @return The uri unchanged.
+     */
+    @Override
+    public String encode(String uri)
+    {
+        return uri;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param o
+     * @return <code>true</code> of the object <code>o</code> is a {@link FileResource} pointing to the same file as this resource. 
+     */
+    @Override
+    public boolean equals( Object o)
+    {
+        if (this == o)
+            return true;
+
+        if (null == o || ! (o instanceof FileResource))
+            return false;
+
+        FileResource f=(FileResource)o;
+        return f._file == _file || (null != _file && _file.equals(f._file));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the hashcode.
+     */
+    @Override
+    public int hashCode()
+    {
+       return null == _file ? super.hashCode() : _file.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (isDirectory())
+        {
+            IO.copyDir(getFile(),destination);
+        }
+        else
+        {
+            if (destination.exists())
+                throw new IllegalArgumentException(destination+" exists");
+            IO.copy(getFile(),destination);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/JarFileResource.java b/src/java/org/eclipse/jetty/util/resource/JarFileResource.java
new file mode 100644
index 0000000..ae33ac1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/JarFileResource.java
@@ -0,0 +1,435 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+class JarFileResource extends JarResource
+{
+    private static final Logger LOG = Log.getLogger(JarFileResource.class);
+    private JarFile _jarFile;
+    private File _file;
+    private String[] _list;
+    private JarEntry _entry;
+    private boolean _directory;
+    private String _jarUrl;
+    private String _path;
+    private boolean _exists;
+    
+    /* -------------------------------------------------------- */
+    JarFileResource(URL url)
+    {
+        super(url);
+    }
+    
+    /* ------------------------------------------------------------ */
+    JarFileResource(URL url, boolean useCaches)
+    {
+        super(url, useCaches);
+    }
+   
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void release()
+    {
+        _list=null;
+        _entry=null;
+        _file=null;
+        //if the jvm is not doing url caching, then the JarFiles will not be cached either,
+        //and so they are safe to close
+        if (!getUseCaches())
+        {
+            if ( _jarFile != null )
+            {
+                try
+                {
+                    LOG.debug("Closing JarFile "+_jarFile.getName());
+                    _jarFile.close();
+                }
+                catch ( IOException ioe )
+                {
+                    LOG.ignore(ioe);
+                }
+            }
+        }
+        _jarFile=null;
+        super.release();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected boolean checkConnection()
+    {
+        try
+        {
+            super.checkConnection();
+        }
+        finally
+        {
+            if (_jarConnection==null)
+            {
+                _entry=null;
+                _file=null;
+                _jarFile=null;
+                _list=null;
+            }
+        }
+        return _jarFile!=null;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized void newConnection()
+        throws IOException
+    {
+        super.newConnection();
+        
+        _entry=null;
+        _file=null;
+        _jarFile=null;
+        _list=null;
+        
+        int sep = _urlString.indexOf("!/");
+        _jarUrl=_urlString.substring(0,sep+2);
+        _path=_urlString.substring(sep+2);
+        if (_path.length()==0)
+            _path=null;   
+        _jarFile=_jarConnection.getJarFile();
+        _file=new File(_jarFile.getName());
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_exists)
+            return true;
+
+        if (_urlString.endsWith("!/"))
+        {
+            
+            String file_url=_urlString.substring(4,_urlString.length()-2);
+            try{return newResource(file_url).exists();}
+            catch(Exception e) {LOG.ignore(e); return false;}
+        }
+        
+        boolean check=checkConnection();
+        
+        // Is this a root URL?
+        if (_jarUrl!=null && _path==null)
+        {
+            // Then if it exists it is a directory
+            _directory=check;
+            return true;
+        }
+        else 
+        {
+            // Can we find a file for it?
+            JarFile jarFile=null;
+            if (check)
+                // Yes
+                jarFile=_jarFile;
+            else
+            {
+                // No - so lets look if the root entry exists.
+                try
+                {
+                    JarURLConnection c=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                    c.setUseCaches(getUseCaches());
+                    jarFile=c.getJarFile();
+                }
+                catch(Exception e)
+                {
+                       LOG.ignore(e);
+                }
+            }
+
+            // Do we need to look more closely?
+            if (jarFile!=null && _entry==null && !_directory)
+            {
+                // OK - we have a JarFile, lets look at the entries for our path
+                Enumeration<JarEntry> e=jarFile.entries();
+                while(e.hasMoreElements())
+                {
+                    JarEntry entry = (JarEntry) e.nextElement();
+                    String name=entry.getName().replace('\\','/');
+                    
+                    // Do we have a match
+                    if (name.equals(_path))
+                    {
+                        _entry=entry;
+                        // Is the match a directory
+                        _directory=_path.endsWith("/");
+                        break;
+                    }
+                    else if (_path.endsWith("/"))
+                    {
+                        if (name.startsWith(_path))
+                        {
+                            _directory=true;
+                            break;
+                        }
+                    }
+                    else if (name.startsWith(_path) && name.length()>_path.length() && name.charAt(_path.length())=='/')
+                    {
+                        _directory=true;
+                        break;
+                    }
+                }
+
+                if (_directory && !_urlString.endsWith("/"))
+                {
+                    _urlString+="/";
+                    try
+                    {
+                        _url=new URL(_urlString);
+                    }
+                    catch(MalformedURLException ex)
+                    {
+                        LOG.warn(ex);
+                    }
+                }
+            }
+        }    
+        
+        _exists= ( _directory || _entry!=null);
+        return _exists;
+    }
+
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return _urlString.endsWith("/") || exists() && _directory;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection() && _file!=null)
+        {
+            if (exists() && _entry!=null)
+                return _entry.getTime();
+            return _file.lastModified();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized String[] list()
+    {
+        if (isDirectory() && _list==null)
+        {
+            List<String> list = null;
+            try
+            {
+                list = listEntries();
+            }
+            catch (Exception e)
+            {
+                //Sun's JarURLConnection impl for jar: protocol will close a JarFile in its connect() method if
+                //useCaches == false (eg someone called URLConnection with defaultUseCaches==true).
+                //As their sun.net.www.protocol.jar package caches JarFiles and/or connections, we can wind up in 
+                //the situation where the JarFile we have remembered in our _jarFile member has actually been closed
+                //by other code.
+                //So, do one retry to drop a connection and get a fresh JarFile
+                LOG.warn("Retrying list:"+e);
+                LOG.debug(e);
+                release();
+                list = listEntries();
+            }
+
+            if (list != null)
+            {
+                _list=new String[list.size()];
+                list.toArray(_list);
+            }  
+        }
+        return _list;
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    private List<String> listEntries ()
+    {
+        checkConnection();
+        
+        ArrayList<String> list = new ArrayList<String>(32);
+        JarFile jarFile=_jarFile;
+        if(jarFile==null)
+        {
+            try
+            {
+                JarURLConnection jc=(JarURLConnection)((new URL(_jarUrl)).openConnection());
+                jc.setUseCaches(getUseCaches());
+                jarFile=jc.getJarFile();
+            }
+            catch(Exception e)
+            {
+
+                e.printStackTrace();
+                 LOG.ignore(e);
+            }
+        }
+        
+        Enumeration<JarEntry> e=jarFile.entries();
+        String dir=_urlString.substring(_urlString.indexOf("!/")+2);
+        while(e.hasMoreElements())
+        {
+            JarEntry entry = e.nextElement();               
+            String name=entry.getName().replace('\\','/');               
+            if(!name.startsWith(dir) || name.length()==dir.length())
+            {
+                continue;
+            }
+            String listName=name.substring(dir.length());               
+            int dash=listName.indexOf('/');
+            if (dash>=0)
+            {
+                //when listing jar:file urls, you get back one
+                //entry for the dir itself, which we ignore
+                if (dash==0 && listName.length()==1)
+                    continue;
+                //when listing jar:file urls, all files and
+                //subdirs have a leading /, which we remove
+                if (dash==0)
+                    listName=listName.substring(dash+1, listName.length());
+                else
+                    listName=listName.substring(0,dash+1);
+                
+                if (list.contains(listName))
+                    continue;
+            }
+            
+            list.add(listName);
+        }
+        
+        return list;
+    }
+    
+    
+    
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (isDirectory())
+            return -1;
+
+        if (_entry!=null)
+            return _entry.getSize();
+        
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Encode according to this resource type.
+     * File URIs are not encoded.
+     * @param uri URI to encode.
+     * @return The uri unchanged.
+     */
+    @Override
+    public String encode(String uri)
+    {
+        return uri;
+    }
+
+    
+    /**
+     * Take a Resource that possibly might use URLConnection caching
+     * and turn it into one that doesn't.
+     * @param resource
+     * @return the non-caching resource
+     */
+    public static Resource getNonCachingResource (Resource resource)
+    {
+        if (!(resource instanceof JarFileResource))
+            return resource;
+        
+        JarFileResource oldResource = (JarFileResource)resource;
+        
+        JarFileResource newResource = new JarFileResource(oldResource.getURL(), false);
+        return newResource;
+        
+    }
+    
+    /**
+     * Check if this jar:file: resource is contained in the
+     * named resource. Eg <code>jar:file:///a/b/c/foo.jar!/x.html</code> isContainedIn <code>file:///a/b/c/foo.jar</code>
+     * @param resource
+     * @return true if resource is contained in the named resource
+     * @throws MalformedURLException
+     */
+    @Override
+    public boolean isContainedIn (Resource resource) 
+    throws MalformedURLException
+    {
+        String string = _urlString;
+        int index = string.indexOf("!/");
+        if (index > 0)
+            string = string.substring(0,index);
+        if (string.startsWith("jar:"))
+            string = string.substring(4);
+        URL url = new URL(string);
+        return url.sameFile(resource.getURL());     
+    }
+}
+
+
+
+
+
+
+
+
diff --git a/src/java/org/eclipse/jetty/util/resource/JarResource.java b/src/java/org/eclipse/jetty/util/resource/JarResource.java
new file mode 100644
index 0000000..bc88e78
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/JarResource.java
@@ -0,0 +1,273 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+public class JarResource extends URLResource
+{
+    private static final Logger LOG = Log.getLogger(JarResource.class);
+    protected JarURLConnection _jarConnection;
+    
+    /* -------------------------------------------------------- */
+    JarResource(URL url)
+    {
+        super(url,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    JarResource(URL url, boolean useCaches)
+    {
+        super(url, null, useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public synchronized void release()
+    {
+        _jarConnection=null;
+        super.release();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized boolean checkConnection()
+    {
+        super.checkConnection();
+        try
+        {
+            if (_jarConnection!=_connection)
+                newConnection();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+            _jarConnection=null;
+        }
+        
+        return _jarConnection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass) 
+     */
+    protected void newConnection() throws IOException
+    {
+        _jarConnection=(JarURLConnection)_connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        if (_urlString.endsWith("!/"))
+            return checkConnection();
+        else
+            return super.exists();
+    }    
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream()
+        throws java.io.IOException
+    {     
+        checkConnection();
+        if (!_urlString.endsWith("!/"))
+            return new FilterInputStream(super.getInputStream()) 
+            {
+                @Override
+                public void close() throws IOException {this.in=IO.getClosedStream();}
+            };
+
+        URL url = new URL(_urlString.substring(4,_urlString.length()-2));      
+        InputStream is = url.openStream();
+        return is;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File directory)
+        throws IOException
+    {
+        if (!exists())
+            return;
+        
+        if(LOG.isDebugEnabled())
+            LOG.debug("Extract "+this+" to "+directory);
+        
+        String urlString = this.getURL().toExternalForm().trim();
+        int endOfJarUrl = urlString.indexOf("!/");
+        int startOfJarUrl = (endOfJarUrl >= 0?4:0);
+        
+        if (endOfJarUrl < 0)
+            throw new IOException("Not a valid jar url: "+urlString);
+        
+        URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
+        String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
+        boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
+      
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
+        
+        InputStream is = jarFileURL.openConnection().getInputStream();
+        JarInputStream jin = new JarInputStream(is);
+        JarEntry entry;
+        boolean shouldExtract;
+        while((entry=jin.getNextJarEntry())!=null)
+        {
+            String entryName = entry.getName();
+            if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
+            { 
+                // is the subentry really a dir?
+                if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
+                        subEntryIsDir=true;
+                
+                //if there is a particular subEntry that we are looking for, only
+                //extract it.
+                if (subEntryIsDir)
+                {
+                    //if it is a subdirectory we are looking for, then we
+                    //are looking to extract its contents into the target
+                    //directory. Remove the name of the subdirectory so
+                    //that we don't wind up creating it too.
+                    entryName = entryName.substring(subEntryName.length());
+                    if (!entryName.equals(""))
+                    {
+                        //the entry is 
+                        shouldExtract = true;                   
+                    }
+                    else
+                        shouldExtract = false;
+                }
+                else
+                    shouldExtract = true;
+            }
+            else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
+            {
+                //there is a particular entry we are looking for, and this one
+                //isn't it
+                shouldExtract = false;
+            }
+            else
+            {
+                //we are extracting everything
+                shouldExtract =  true;
+            }
+                
+            
+            if (!shouldExtract)
+            {
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("Skipping entry: "+entryName);
+                continue;
+            }
+                
+            String dotCheck = entryName.replace('\\', '/');   
+            dotCheck = URIUtil.canonicalPath(dotCheck);
+            if (dotCheck == null)
+            {
+                if (LOG.isDebugEnabled()) 
+                    LOG.debug("Invalid entry: "+entryName);
+                continue;
+            }
+
+            File file=new File(directory,entryName);
+     
+            if (entry.isDirectory())
+            {
+                // Make directory
+                if (!file.exists())
+                    file.mkdirs();
+            }
+            else
+            {
+                // make directory (some jars don't list dirs)
+                File dir = new File(file.getParent());
+                if (!dir.exists())
+                    dir.mkdirs();
+
+                // Make file
+                FileOutputStream fout = null;
+                try
+                {
+                    fout = new FileOutputStream(file);
+                    IO.copy(jin,fout);
+                }
+                finally
+                {
+                    IO.close(fout);
+                }
+
+                // touch the file.
+                if (entry.getTime()>=0)
+                    file.setLastModified(entry.getTime());
+            }
+        }
+        
+        if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
+        {
+            Manifest manifest = jin.getManifest();
+            if (manifest != null)
+            {
+                File metaInf = new File (directory, "META-INF");
+                metaInf.mkdir();
+                File f = new File(metaInf, "MANIFEST.MF");
+                FileOutputStream fout = new FileOutputStream(f);
+                manifest.write(fout);
+                fout.close();   
+            }
+        }
+        IO.close(jin);
+    }   
+    
+    public static Resource newJarResource(Resource resource) throws IOException
+    {
+        if (resource instanceof JarResource)
+            return resource;
+        return Resource.newResource("jar:" + resource + "!/");
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/Resource.java b/src/java/org/eclipse/jetty/util/resource/Resource.java
new file mode 100644
index 0000000..e2275bf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/Resource.java
@@ -0,0 +1,678 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** 
+ * Abstract resource class.
+ */
+public abstract class Resource implements ResourceFactory
+{
+    private static final Logger LOG = Log.getLogger(Resource.class);
+    public static boolean __defaultUseCaches = true;
+    volatile Object _associate;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Change the default setting for url connection caches.
+     * Subsequent URLConnections will use this default.
+     * @param useCaches
+     */
+    public static void setDefaultUseCaches (boolean useCaches)
+    {
+        __defaultUseCaches=useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static boolean getDefaultUseCaches ()
+    {
+        return __defaultUseCaches;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a uri.
+     * @param uri A URI.
+     * @return A Resource object.
+     * @throws IOException Problem accessing URI
+     */
+    public static Resource newResource(URI uri)
+        throws IOException
+    {
+        return newResource(uri.toURL());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a url.
+     * @param url A URL.
+     * @return A Resource object.
+     * @throws IOException Problem accessing URL
+     */
+    public static Resource newResource(URL url)
+        throws IOException
+    {
+        return newResource(url, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */   
+    /**
+     * Construct a resource from a url.
+     * @param url the url for which to make the resource
+     * @param useCaches true enables URLConnection caching if applicable to the type of resource
+     * @return
+     */
+    static Resource newResource(URL url, boolean useCaches)
+    {
+        if (url==null)
+            return null;
+
+        String url_string=url.toExternalForm();
+        if( url_string.startsWith( "file:"))
+        {
+            try
+            {
+                FileResource fileResource= new FileResource(url);
+                return fileResource;
+            }
+            catch(Exception e)
+            {
+                LOG.debug(Log.EXCEPTION,e);
+                return new BadResource(url,e.toString());
+            }
+        }
+        else if( url_string.startsWith( "jar:file:"))
+        {
+            return new JarFileResource(url, useCaches);
+        }
+        else if( url_string.startsWith( "jar:"))
+        {
+            return new JarResource(url, useCaches);
+        }
+
+        return new URLResource(url,null,useCaches);
+    }
+
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @return A Resource object.
+     */
+    public static Resource newResource(String resource)
+        throws MalformedURLException, IOException
+    {
+        return newResource(resource, __defaultUseCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Construct a resource from a string.
+     * @param resource A URL or filename.
+     * @param useCaches controls URLConnection caching
+     * @return A Resource object.
+     */
+    public static Resource newResource (String resource, boolean useCaches)       
+    throws MalformedURLException, IOException
+    {
+        URL url=null;
+        try
+        {
+            // Try to format as a URL?
+            url = new URL(resource);
+        }
+        catch(MalformedURLException e)
+        {
+            if(!resource.startsWith("ftp:") &&
+               !resource.startsWith("file:") &&
+               !resource.startsWith("jar:"))
+            {
+                try
+                {
+                    // It's a file.
+                    if (resource.startsWith("./"))
+                        resource=resource.substring(2);
+                    
+                    File file=new File(resource).getCanonicalFile();
+                    url=Resource.toURL(file);            
+                    
+                    URLConnection connection=url.openConnection();
+                    connection.setUseCaches(useCaches);
+                    return new FileResource(url,connection,file);
+                }
+                catch(Exception e2)
+                {
+                    LOG.debug(Log.EXCEPTION,e2);
+                    throw e;
+                }
+            }
+            else
+            {
+                LOG.warn("Bad Resource: "+resource);
+                throw e;
+            }
+        }
+
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Resource newResource (File file)
+    throws MalformedURLException, IOException
+    {
+        file = file.getCanonicalFile();
+        URL url = Resource.toURL(file);
+
+        URLConnection connection = url.openConnection();
+        FileResource fileResource = new FileResource(url, connection, file);
+        return fileResource;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Construct a system resource from a string.
+     * The resource is tried as classloader resource before being
+     * treated as a normal resource.
+     * @param resource Resource as string representation 
+     * @return The new Resource
+     * @throws IOException Problem accessing resource.
+     */
+    public static Resource newSystemResource(String resource)
+        throws IOException
+    {
+        URL url=null;
+        // Try to format as a URL?
+        ClassLoader loader=Thread.currentThread().getContextClassLoader();
+        if (loader!=null)
+        {
+            try
+            {
+                url = loader.getResource(resource);
+                if (url == null && resource.startsWith("/"))
+                    url = loader.getResource(resource.substring(1));
+            }
+            catch (IllegalArgumentException e)
+            {
+                // Catches scenario where a bad Windows path like "C:\dev" is
+                // improperly escaped, which various downstream classloaders
+                // tend to have a problem with
+                url = null;
+            }
+        }
+        if (url==null)
+        {
+            loader=Resource.class.getClassLoader();
+            if (loader!=null)
+            {
+                url=loader.getResource(resource);
+                if (url==null && resource.startsWith("/"))
+                    url=loader.getResource(resource.substring(1));
+            }
+        }
+        
+        if (url==null)
+        {
+            url=ClassLoader.getSystemResource(resource);
+            if (url==null && resource.startsWith("/"))
+                url=ClassLoader.getSystemResource(resource.substring(1));
+        }
+        
+        if (url==null)
+            return null;
+        
+        return newResource(url);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     */
+    public static Resource newClassPathResource(String resource)
+    {
+        return newClassPathResource(resource,true,false);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Find a classpath resource.
+     * The {@link java.lang.Class#getResource(String)} method is used to lookup the resource. If it is not
+     * found, then the {@link Loader#getResource(Class, String, boolean)} method is used.
+     * If it is still not found, then {@link ClassLoader#getSystemResource(String)} is used.
+     * Unlike {@link ClassLoader#getSystemResource(String)} this method does not check for normal resources.
+     * @param name The relative name of the resource
+     * @param useCaches True if URL caches are to be used.
+     * @param checkParents True if forced searching of parent Classloaders is performed to work around 
+     * loaders with inverted priorities
+     * @return Resource or null
+     */
+    public static Resource newClassPathResource(String name,boolean useCaches,boolean checkParents)
+    {
+        URL url=Resource.class.getResource(name);
+        
+        if (url==null)
+            url=Loader.getResource(Resource.class,name,checkParents);
+        if (url==null)
+            return null;
+        return newResource(url,useCaches);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static boolean isContainedIn (Resource r, Resource containingResource) throws MalformedURLException
+    {
+        return r.isContainedIn(containingResource);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void finalize()
+    {
+        release();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public abstract boolean isContainedIn (Resource r) throws MalformedURLException;
+    
+    
+    /* ------------------------------------------------------------ */
+    /** Release any temporary resources held by the resource.
+     */
+    public abstract void release();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresened resource exists.
+     */
+    public abstract boolean exists();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    public abstract boolean isDirectory();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    public abstract long lastModified();
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    public abstract long length();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    public abstract URL getURL();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URI representing the given resource
+     */
+    public URI getURI()
+    {
+        try
+        {
+            return getURL().toURI();
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    public abstract File getFile()
+        throws IOException;
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    public abstract String getName();
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    public abstract InputStream getInputStream()
+        throws java.io.IOException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an output stream to the resource
+     */
+    public abstract OutputStream getOutputStream()
+        throws java.io.IOException, SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    public abstract boolean delete()
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    public abstract boolean renameTo( Resource dest)
+        throws SecurityException;
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     * The resource names are not URL encoded.
+     */
+    public abstract String[] list();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name.
+     * @param path The path segment to add, which should be encoded by the
+     * encode method. 
+     */
+    public abstract Resource addPath(String path)
+        throws IOException,MalformedURLException;
+
+    /* ------------------------------------------------------------ */
+    /** Get a resource from withing this resource.
+     * <p>
+     * This method is essentially an alias for {@link #addPath(String)}, but without checked exceptions.
+     * This method satisfied the {@link ResourceFactory} interface.
+     * @see org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
+     */
+    public Resource getResource(String path)
+    {
+        try
+        {
+            return addPath(path);
+        }
+        catch(Exception e)
+        {
+            LOG.debug(e);
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Encode according to this resource type.
+     * The default implementation calls URI.encodePath(uri)
+     * @param uri 
+     * @return String encoded for this resource type.
+     */
+    public String encode(String uri)
+    {
+        return URIUtil.encodePath(uri);
+    }
+        
+    /* ------------------------------------------------------------ */
+    public Object getAssociate()
+    {
+        return _associate;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setAssociate(Object o)
+    {
+        _associate=o;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The canonical Alias of this resource or null if none.
+     */
+    public URL getAlias()
+    {
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Get the resource list as a HTML directory listing.
+     * @param base The base URL
+     * @param parent True if the parent directory should be included
+     * @return String of HTML
+     */
+    public String getListHTML(String base,boolean parent)
+        throws IOException
+    {
+        base=URIUtil.canonicalPath(base);
+        if (base==null || !isDirectory())
+            return null;
+        
+        String[] ls = list();
+        if (ls==null)
+            return null;
+        Arrays.sort(ls);
+        
+        String decodedBase = URIUtil.decodePath(base);
+        String title = "Directory: "+deTag(decodedBase);
+
+        StringBuilder buf=new StringBuilder(4096);
+        buf.append("<HTML><HEAD>");
+        buf.append("<LINK HREF=\"").append("jetty-dir.css").append("\" REL=\"stylesheet\" TYPE=\"text/css\"/><TITLE>");
+        buf.append(title);
+        buf.append("</TITLE></HEAD><BODY>\n<H1>");
+        buf.append(title);
+        buf.append("</H1>\n<TABLE BORDER=0>\n");
+        
+        if (parent)
+        {
+            buf.append("<TR><TD><A HREF=\"");
+            buf.append(URIUtil.addPaths(base,"../"));
+            buf.append("\">Parent Directory</A></TD><TD></TD><TD></TD></TR>\n");
+        }
+        
+        String encodedBase = hrefEncodeURI(base);
+        
+        DateFormat dfmt=DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
+                                                       DateFormat.MEDIUM);
+        for (int i=0 ; i< ls.length ; i++)
+        {
+            Resource item = addPath(ls[i]);
+            
+            buf.append("\n<TR><TD><A HREF=\"");
+            String path=URIUtil.addPaths(encodedBase,URIUtil.encodePath(ls[i]));
+            
+            buf.append(path);
+            
+            if (item.isDirectory() && !path.endsWith("/"))
+                buf.append(URIUtil.SLASH);
+            
+            // URIUtil.encodePath(buf,path);
+            buf.append("\">");
+            buf.append(deTag(ls[i]));
+            buf.append("&nbsp;");
+            buf.append("</A></TD><TD ALIGN=right>");
+            buf.append(item.length());
+            buf.append(" bytes&nbsp;</TD><TD>");
+            buf.append(dfmt.format(new Date(item.lastModified())));
+            buf.append("</TD></TR>");
+        }
+        buf.append("</TABLE>\n");
+	buf.append("</BODY></HTML>\n");
+        
+        return buf.toString();
+    }
+    
+    /**
+     * Encode any characters that could break the URI string in an HREF.
+     * Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
+     * 
+     * The above example would parse incorrectly on various browsers as the "<" or '"' characters
+     * would end the href attribute value string prematurely.
+     * 
+     * @param raw the raw text to encode.
+     * @return the defanged text.
+     */
+    private static String hrefEncodeURI(String raw) 
+    {
+        StringBuffer buf = null;
+
+        loop:
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);
+            switch(c)
+            {
+                case '\'':
+                case '"':
+                case '<':
+                case '>':
+                    buf=new StringBuffer(raw.length()<<1);
+                    break loop;
+            }
+        }
+        if (buf==null)
+            return raw;
+
+        for (int i=0;i<raw.length();i++)
+        {
+            char c=raw.charAt(i);       
+            switch(c)
+            {
+              case '"':
+                  buf.append("%22");
+                  continue;
+              case '\'':
+                  buf.append("%27");
+                  continue;
+              case '<':
+                  buf.append("%3C");
+                  continue;
+              case '>':
+                  buf.append("%3E");
+                  continue;
+              default:
+                  buf.append(c);
+                  continue;
+            }
+        }
+
+        return buf.toString();
+    }
+    
+    private static String deTag(String raw) 
+    {
+        return StringUtil.replace( StringUtil.replace(raw,"<","&lt;"), ">", "&gt;");
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @param out 
+     * @param start First byte to write
+     * @param count Bytes to write or -1 for all of them.
+     */
+    public void writeTo(OutputStream out,long start,long count)
+        throws IOException
+    {
+        InputStream in = getInputStream();
+        try
+        {
+            in.skip(start);
+            if (count<0)
+                IO.copy(in,out);
+            else
+                IO.copy(in,out,count);
+        }
+        finally
+        {
+            in.close();
+        }
+    }    
+    
+    /* ------------------------------------------------------------ */
+    public void copyTo(File destination)
+        throws IOException
+    {
+        if (destination.exists())
+            throw new IllegalArgumentException(destination+" exists");
+        writeTo(new FileOutputStream(destination),0,-1);
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getWeakETag()
+    {
+        try
+        {
+            StringBuilder b = new StringBuilder(32);
+            b.append("W/\"");
+            
+            String name=getName();
+            int length=name.length();
+            long lhash=0;
+            for (int i=0; i<length;i++)
+                lhash=31*lhash+name.charAt(i);
+            
+            B64Code.encode(lastModified()^lhash,b);
+            B64Code.encode(length()^lhash,b);
+            b.append('"');
+            return b.toString();
+        } 
+        catch (IOException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Generate a properly encoded URL from a {@link File} instance.
+     * @param file Target file. 
+     * @return URL of the target file.
+     * @throws MalformedURLException 
+     */
+    public static URL toURL(File file) throws MalformedURLException
+    {
+        return file.toURI().toURL();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/ResourceCollection.java b/src/java/org/eclipse/jetty/util/resource/ResourceCollection.java
new file mode 100644
index 0000000..da36018
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/ResourceCollection.java
@@ -0,0 +1,482 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.URIUtil;
+
+/**
+ * A collection of resources (dirs).
+ * Allows webapps to have multiple (static) sources.
+ * The first resource in the collection is the main resource.
+ * If a resource is not found in the main resource, it looks it up in 
+ * the order the resources were constructed.
+ * 
+ * 
+ *
+ */
+public class ResourceCollection extends Resource
+{
+    private Resource[] _resources;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates an empty resource collection.
+     * 
+     * This constructor is used when configuring jetty-maven-plugin.
+     */
+    public ResourceCollection()
+    {
+        _resources = new Resource[0];
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resources to be added to collection
+     */
+    public ResourceCollection(Resource... resources)
+    {
+        List<Resource> list = new ArrayList<Resource>();
+        for (Resource r : resources)
+        {
+            if (r==null)
+                continue;
+            if (r instanceof ResourceCollection)
+            {
+                for (Resource r2 : ((ResourceCollection)r).getResources())
+                    list.add(r2);
+            }
+            else
+                list.add(r);
+        }
+        _resources = list.toArray(new Resource[list.size()]);
+        for(Resource r : _resources)
+        {
+            if(!r.exists() || !r.isDirectory())
+                throw new IllegalArgumentException(r + " is not an existing directory.");
+        }
+    }
+    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param resources the resource strings to be added to collection
+     */
+    public ResourceCollection(String[] resources)
+    {
+        _resources = new Resource[resources.length];
+        try
+        {
+            for(int i=0; i<resources.length; i++)
+            {
+                _resources[i] = Resource.newResource(resources[i]);
+                if(!_resources[i].exists() || !_resources[i].isDirectory())
+                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+            }
+        }
+        catch(IllegalArgumentException e)
+        {
+            throw e;
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Instantiates a new resource collection.
+     *
+     * @param csvResources the string containing comma-separated resource strings
+     */
+    public ResourceCollection(String csvResources)
+    {
+        setResourcesAsCSV(csvResources);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Retrieves the resource collection's resources.
+     * 
+     * @return the resource array
+     */
+    public Resource[] getResources()
+    {
+        return _resources;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resource collection's resources.
+     *
+     * @param resources the new resource array
+     */
+    public void setResources(Resource[] resources)
+    {
+        _resources = resources != null ? resources : new Resource[0];
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets the resources as string of comma-separated values.
+     * This method should be used when configuring jetty-maven-plugin.
+     *
+     * @param csvResources the comma-separated string containing
+     *                     one or more resource strings.
+     */
+    public void setResourcesAsCSV(String csvResources)
+    {
+        StringTokenizer tokenizer = new StringTokenizer(csvResources, ",;");
+        int len = tokenizer.countTokens();
+        if(len==0)
+        {
+            throw new IllegalArgumentException("ResourceCollection@setResourcesAsCSV(String) " +
+                    " argument must be a string containing one or more comma-separated resource strings.");
+        }
+        
+        _resources = new Resource[len];
+        try
+        {            
+            for(int i=0; tokenizer.hasMoreTokens(); i++)
+            {
+                _resources[i] = Resource.newResource(tokenizer.nextToken().trim());
+                if(!_resources[i].exists() || !_resources[i].isDirectory())
+                    throw new IllegalArgumentException(_resources[i] + " is not an existing directory.");
+            }
+        }
+        catch(Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path The path segment to add
+     * @return The contained resource (found first) in the collection of resources
+     */
+    @Override
+    public Resource addPath(String path) throws IOException, MalformedURLException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        if(path==null)
+            throw new MalformedURLException();
+        
+        if(path.length()==0 || URIUtil.SLASH.equals(path))
+            return this;
+        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;       
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resource!=null)
+                {
+                    resources = new ArrayList<Resource>();
+                    resources.add(resource);
+                    resource=null;
+                }
+                resources.add(r);
+            }
+        }
+
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return new ResourceCollection(resources.toArray(new Resource[resources.size()]));
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param path
+     * @return the resource(file) if found, returns a list of resource dirs if its a dir, else null.
+     * @throws IOException
+     * @throws MalformedURLException
+     */
+    protected Object findResource(String path) throws IOException, MalformedURLException
+    {        
+        Resource resource=null;
+        ArrayList<Resource> resources = null;
+        int i=0;
+        for(; i<_resources.length; i++)
+        {
+            resource = _resources[i].addPath(path);  
+            if (resource.exists())
+            {
+                if (resource.isDirectory())
+                    break;
+               
+                return resource;
+            }
+        }  
+
+        for(i++; i<_resources.length; i++)
+        {
+            Resource r = _resources[i].addPath(path); 
+            if (r.exists() && r.isDirectory())
+            {
+                if (resource!=null)
+                {
+                    resources = new ArrayList<Resource>();
+                    resources.add(resource);
+                }
+                resources.add(r);
+            }
+        }
+        
+        if (resource!=null)
+            return resource;
+        if (resources!=null)
+            return resources;
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean delete() throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean exists()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public File getFile() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            File f = r.getFile();
+            if(f!=null)
+                return f;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            InputStream is = r.getInputStream();
+            if(is!=null)
+                return is;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public String getName()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            String name = r.getName();
+            if(name!=null)
+                return name;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public OutputStream getOutputStream() throws IOException, SecurityException
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            OutputStream os = r.getOutputStream();
+            if(os!=null)
+                return os;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public URL getURL()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            URL url = r.getURL();
+            if(url!=null)
+                return url;
+        }
+        return null;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isDirectory()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        return true;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long lastModified()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+        {
+            long lm = r.lastModified();
+            if (lm!=-1)
+                return lm;
+        }
+        return -1;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public long length()
+    {
+        return -1;
+    }    
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The list of resource names(merged) contained in the collection of resources.
+     */    
+    @Override
+    public String[] list()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        HashSet<String> set = new HashSet<String>();
+        for(Resource r : _resources)
+        {
+            for(String s : r.list())
+                set.add(s);
+        }
+        String[] result=set.toArray(new String[set.size()]);
+        Arrays.sort(result);
+        return result;
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public void release()
+    {
+        if(_resources==null)
+            throw new IllegalStateException("*resources* not set.");
+        
+        for(Resource r : _resources)
+            r.release();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean renameTo(Resource dest) throws SecurityException
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void copyTo(File destination)
+        throws IOException
+    {
+        for (int r=_resources.length;r-->0;)
+            _resources[r].copyTo(destination);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the list of resources separated by a path separator
+     */
+    @Override
+    public String toString()
+    {
+        if(_resources==null)
+            return "[]";
+        
+        return String.valueOf(Arrays.asList(_resources));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn(Resource r) throws MalformedURLException
+    {
+        // TODO could look at implementing the semantic of is this collection a subset of the Resource r?
+        return false;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/ResourceFactory.java b/src/java/org/eclipse/jetty/util/resource/ResourceFactory.java
new file mode 100644
index 0000000..707a672
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/ResourceFactory.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+
+/* ------------------------------------------------------------ */
+/** ResourceFactory.
+ */
+public interface ResourceFactory
+{
+    
+    /* ------------------------------------------------------------ */
+    /** Get a resource for a path.
+     * @param path The path to the resource
+     * @return The resource or null 
+     */
+    Resource getResource(String path);
+}
diff --git a/src/java/org/eclipse/jetty/util/resource/URLResource.java b/src/java/org/eclipse/jetty/util/resource/URLResource.java
new file mode 100644
index 0000000..1e19385
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/resource/URLResource.java
@@ -0,0 +1,321 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.resource;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.Permission;
+
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/** Abstract resource class.
+ */
+public class URLResource extends Resource
+{
+    private static final Logger LOG = Log.getLogger(URLResource.class);
+    protected URL _url;
+    protected String _urlString;
+    
+    protected URLConnection _connection;
+    protected InputStream _in=null;
+    transient boolean _useCaches = Resource.__defaultUseCaches;
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource(URL url, URLConnection connection)
+    {
+        _url = url;
+        _urlString=_url.toString();
+        _connection=connection;
+    }
+    
+    /* ------------------------------------------------------------ */
+    protected URLResource (URL url, URLConnection connection, boolean useCaches)
+    {
+        this (url, connection);
+        _useCaches = useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected synchronized boolean checkConnection()
+    {
+        if (_connection==null)
+        {
+            try{
+                _connection=_url.openConnection();
+                _connection.setUseCaches(_useCaches);
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+        return _connection!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Release any resources held by the resource.
+     */
+    @Override
+    public synchronized void release()
+    {
+        if (_in!=null)
+        {
+            try{_in.close();}catch(IOException e){LOG.ignore(e);}
+            _in=null;
+        }
+
+        if (_connection!=null)
+            _connection=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the represented resource exists.
+     */
+    @Override
+    public boolean exists()
+    {
+        try
+        {
+            synchronized(this)
+            {
+                if (checkConnection() && _in==null )
+                    _in = _connection.getInputStream();
+            }
+        }
+        catch (IOException e)
+        {
+            LOG.ignore(e);
+        }
+        return _in!=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns true if the respresenetd resource is a container/directory.
+     * If the resource is not a file, resources ending with "/" are
+     * considered directories.
+     */
+    @Override
+    public boolean isDirectory()
+    {
+        return exists() && _url.toString().endsWith("/");
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the last modified time
+     */
+    @Override
+    public long lastModified()
+    {
+        if (checkConnection())
+            return _connection.getLastModified();
+        return -1;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Return the length of the resource
+     */
+    @Override
+    public long length()
+    {
+        if (checkConnection())
+            return _connection.getContentLength();
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an URL representing the given resource
+     */
+    @Override
+    public URL getURL()
+    {
+        return _url;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an File representing the given resource or NULL if this
+     * is not possible.
+     */
+    @Override
+    public File getFile()
+        throws IOException
+    {
+        // Try the permission hack
+        if (checkConnection())
+        {
+            Permission perm = _connection.getPermission();
+            if (perm instanceof java.io.FilePermission)
+                return new File(perm.getName());
+        }
+
+        // Try the URL file arg
+        try {return new File(_url.getFile());}
+        catch(Exception e) {LOG.ignore(e);}
+
+        // Don't know the file
+        return null;    
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the name of the resource
+     */
+    @Override
+    public String getName()
+    {
+        return _url.toExternalForm();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an input stream to the resource
+     */
+    @Override
+    public synchronized InputStream getInputStream()
+        throws java.io.IOException
+    {
+        if (!checkConnection())
+            throw new IOException( "Invalid resource");
+
+        try
+        {    
+            if( _in != null)
+            {
+                InputStream in = _in;
+                _in=null;
+                return in;
+            }
+            return _connection.getInputStream();
+        }
+        finally
+        {
+            _connection=null;
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns an output stream to the resource
+     */
+    @Override
+    public OutputStream getOutputStream()
+        throws java.io.IOException, SecurityException
+    {
+        throw new IOException( "Output not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Deletes the given resource
+     */
+    @Override
+    public boolean delete()
+        throws SecurityException
+    {
+        throw new SecurityException( "Delete not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Rename the given resource
+     */
+    @Override
+    public boolean renameTo( Resource dest)
+        throws SecurityException
+    {
+        throw new SecurityException( "RenameTo not supported");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns a list of resource names contained in the given resource
+     */
+    @Override
+    public String[] list()
+    {
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the resource contained inside the current resource with the
+     * given name
+     */
+    @Override
+    public Resource addPath(String path)
+        throws IOException,MalformedURLException
+    {
+        if (path==null)
+            return null;
+
+        path = URIUtil.canonicalPath(path);
+
+        return newResource(URIUtil.addPaths(_url.toExternalForm(),path));
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _urlString;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return _urlString.hashCode();
+    }
+    
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals( Object o)
+    {
+        return o instanceof URLResource && _urlString.equals(((URLResource)o)._urlString);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean getUseCaches ()
+    {
+        return _useCaches;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean isContainedIn (Resource containingResource) throws MalformedURLException
+    {
+        return false; //TODO check this!
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/security/B64Code.java b/src/java/org/eclipse/jetty/util/security/B64Code.java
new file mode 100644
index 0000000..ef67bbf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/B64Code.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.security;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * @deprecated use {@link org.eclipse.jetty.util.B64Code}
+ */
+@Deprecated 
+public class B64Code extends org.eclipse.jetty.util.B64Code
+{
+    public B64Code()
+    {
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/security/CertificateUtils.java b/src/java/org/eclipse/jetty/util/security/CertificateUtils.java
new file mode 100644
index 0000000..4ed9ae2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/CertificateUtils.java
@@ -0,0 +1,94 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CRL;
+import java.security.cert.CertificateFactory;
+import java.util.Collection;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+public class CertificateUtils
+{
+    /* ------------------------------------------------------------ */
+    public static KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        KeyStore keystore = null;
+
+        if (storeStream != null || storePath != null)
+        {
+            InputStream inStream = storeStream;
+            try
+            {
+                if (inStream == null)
+                {
+                    inStream = Resource.newResource(storePath).getInputStream();
+                }
+                
+                if (storeProvider != null)
+                {
+                    keystore = KeyStore.getInstance(storeType, storeProvider);
+                }
+                else
+                {
+                    keystore = KeyStore.getInstance(storeType);
+                }
+    
+                keystore.load(inStream, storePassword == null ? null : storePassword.toCharArray());
+            }
+            finally
+            {
+                if (inStream != null)
+                {
+                    inStream.close();
+                }
+            }
+        }
+        
+        return keystore;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        Collection<? extends CRL> crlList = null;
+
+        if (crlPath != null)
+        {
+            InputStream in = null;
+            try
+            {
+                in = Resource.newResource(crlPath).getInputStream();
+                crlList = CertificateFactory.getInstance("X.509").generateCRLs(in);
+            }
+            finally
+            {
+                if (in != null)
+                {
+                    in.close();
+                }
+            }
+        }
+
+        return crlList;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/util/security/CertificateValidator.java b/src/java/org/eclipse/jetty/util/security/CertificateValidator.java
new file mode 100644
index 0000000..2ead387
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/CertificateValidator.java
@@ -0,0 +1,343 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertPathBuilder;
+import java.security.cert.CertPathBuilderResult;
+import java.security.cert.CertPathValidator;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Convenience class to handle validation of certificates, aliases and keystores
+ *
+ * Allows specifying Certificate Revocation List (CRL), as well as enabling
+ * CRL Distribution Points Protocol (CRLDP) certificate extension support,
+ * and also enabling On-Line Certificate Status Protocol (OCSP) support.
+ * 
+ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and
+ * operational, otherwise certificate validation *WILL FAIL* unconditionally.
+ */
+public class CertificateValidator
+{
+    private static final Logger LOG = Log.getLogger(CertificateValidator.class);
+    private static AtomicLong __aliasCount = new AtomicLong();
+    
+    private KeyStore _trustStore;
+    private Collection<? extends CRL> _crls;
+
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+    
+    /**
+     * creates an instance of the certificate validator 
+     *
+     * @param trustStore 
+     * @param crls
+     */
+    public CertificateValidator(KeyStore trustStore, Collection<? extends CRL> crls)
+    {
+        if (trustStore == null)
+        {
+            throw new InvalidParameterException("TrustStore must be specified for CertificateValidator.");
+        }
+        
+        _trustStore = trustStore;
+        _crls = crls;
+    }
+    
+    /**
+     * validates all aliases inside of a given keystore
+     * 
+     * @param keyStore
+     * @throws CertificateException
+     */
+    public void validate( KeyStore keyStore ) throws CertificateException
+    {
+        try
+        {
+            Enumeration<String> aliases = keyStore.aliases();
+            
+            for ( ; aliases.hasMoreElements(); )
+            {
+                String alias = aliases.nextElement();
+                
+                validate(keyStore,alias);
+            }
+            
+        }
+        catch ( KeyStoreException kse )
+        {
+            throw new CertificateException("Unable to retrieve aliases from keystore", kse);
+        }
+    }
+    
+
+    /**
+     * validates a specific alias inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param keyAlias
+     * @return the keyAlias if valid
+     * @throws CertificateException
+     */
+    public String validate(KeyStore keyStore, String keyAlias) throws CertificateException
+    {
+        String result = null;
+
+        if (keyAlias != null)
+        {
+            try
+            {
+                validate(keyStore, keyStore.getCertificate(keyAlias));
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        " for alias [" + keyAlias + "]: " + kse.getMessage(), kse);
+            }
+            result = keyAlias;            
+        }
+        
+        return result;
+    }
+    
+    /**
+     * validates a specific certificate inside of the keystore being passed in
+     * 
+     * @param keyStore
+     * @param cert
+     * @throws CertificateException
+     */
+    public void validate(KeyStore keyStore, Certificate cert) throws CertificateException
+    {
+        Certificate[] certChain = null;
+        
+        if (cert != null && cert instanceof X509Certificate)
+        {
+            ((X509Certificate)cert).checkValidity();
+            
+            String certAlias = null;
+            try
+            {
+                if (keyStore == null)
+                {
+                    throw new InvalidParameterException("Keystore cannot be null");
+                }
+
+                certAlias = keyStore.getCertificateAlias((X509Certificate)cert);
+                if (certAlias == null)
+                {
+                    certAlias = "JETTY" + String.format("%016X",__aliasCount.incrementAndGet());
+                    keyStore.setCertificateEntry(certAlias, cert);
+                }
+                
+                certChain = keyStore.getCertificateChain(certAlias);
+                if (certChain == null || certChain.length == 0)
+                {
+                    throw new IllegalStateException("Unable to retrieve certificate chain");
+                }
+            }
+            catch (KeyStoreException kse)
+            {
+                LOG.debug(kse);
+                throw new CertificateException("Unable to validate certificate" +
+                        (certAlias == null ? "":" for alias [" +certAlias + "]") + ": " + kse.getMessage(), kse);
+            }
+            
+            validate(certChain);
+        } 
+    }
+    
+    public void validate(Certificate[] certChain) throws CertificateException
+    {
+        try
+        {
+            ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
+            for (Certificate item : certChain)
+            {
+                if (item == null)
+                    continue;
+                
+                if (!(item instanceof X509Certificate))
+                {
+                    throw new IllegalStateException("Invalid certificate type in chain");
+                }
+                
+                certList.add((X509Certificate)item);
+            }
+    
+            if (certList.isEmpty())
+            {
+                throw new IllegalStateException("Invalid certificate chain");
+                
+            }
+    
+            X509CertSelector certSelect = new X509CertSelector();
+            certSelect.setCertificate(certList.get(0));
+            
+            // Configure certification path builder parameters
+            PKIXBuilderParameters pbParams = new PKIXBuilderParameters(_trustStore, certSelect);
+            pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList)));
+    
+            // Set maximum certification path length
+            pbParams.setMaxPathLength(_maxCertPathLength);
+    
+            // Enable revocation checking
+            pbParams.setRevocationEnabled(true);
+    
+            // Set static Certificate Revocation List
+            if (_crls != null && !_crls.isEmpty())
+            {
+                pbParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(_crls)));
+            }
+    
+            // Enable On-Line Certificate Status Protocol (OCSP) support
+            if (_enableOCSP)
+            {
+                Security.setProperty("ocsp.enable","true");
+            }
+            // Enable Certificate Revocation List Distribution Points (CRLDP) support
+            if (_enableCRLDP)
+            {
+                System.setProperty("com.sun.security.enableCRLDP","true");
+            }
+    
+            // Build certification path
+            CertPathBuilderResult buildResult = CertPathBuilder.getInstance("PKIX").build(pbParams);               
+            
+            // Validate certification path
+            CertPathValidator.getInstance("PKIX").validate(buildResult.getCertPath(),pbParams);
+        }
+        catch (GeneralSecurityException gse)
+        {
+            LOG.debug(gse);
+            throw new CertificateException("Unable to validate certificate: " + gse.getMessage(), gse);
+        }
+    }
+
+    public KeyStore getTrustStore()
+    {
+        return _trustStore;
+    }
+
+    public Collection<? extends CRL> getCrls()
+    {
+        return _crls;
+    }
+
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        _maxCertPathLength = maxCertPathLength;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** 
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        _ocspResponderURL = ocspResponderURL;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/security/Constraint.java b/src/java/org/eclipse/jetty/util/security/Constraint.java
new file mode 100644
index 0000000..779b409
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/Constraint.java
@@ -0,0 +1,226 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/* ------------------------------------------------------------ */
+/**
+ * Describe an auth and/or data constraint.
+ * 
+ * 
+ */
+public class Constraint implements Cloneable, Serializable
+{
+    /* ------------------------------------------------------------ */
+    public final static String __BASIC_AUTH = "BASIC";
+
+    public final static String __FORM_AUTH = "FORM";
+
+    public final static String __DIGEST_AUTH = "DIGEST";
+
+    public final static String __CERT_AUTH = "CLIENT_CERT";
+
+    public final static String __CERT_AUTH2 = "CLIENT-CERT";
+    
+    public final static String __SPNEGO_AUTH = "SPNEGO";
+    
+    public final static String __NEGOTIATE_AUTH = "NEGOTIATE";
+    
+    public static boolean validateMethod (String method)
+    {
+        if (method == null)
+            return false;
+        method = method.trim();
+        return (method.equals(__FORM_AUTH) 
+                || method.equals(__BASIC_AUTH) 
+                || method.equals (__DIGEST_AUTH) 
+                || method.equals (__CERT_AUTH) 
+                || method.equals(__CERT_AUTH2)
+                || method.equals(__SPNEGO_AUTH)
+                || method.equals(__NEGOTIATE_AUTH));
+    }
+
+    /* ------------------------------------------------------------ */
+    public final static int DC_UNSET = -1, DC_NONE = 0, DC_INTEGRAL = 1, DC_CONFIDENTIAL = 2, DC_FORBIDDEN = 3;
+
+    /* ------------------------------------------------------------ */
+    public final static String NONE = "NONE";
+
+    public final static String ANY_ROLE = "*";
+
+    /* ------------------------------------------------------------ */
+    private String _name;
+
+    private String[] _roles;
+
+    private int _dataConstraint = DC_UNSET;
+
+    private boolean _anyRole = false;
+
+    private boolean _authenticate = false;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public Constraint()
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Conveniance Constructor.
+     * 
+     * @param name
+     * @param role
+     */
+    public Constraint(String name, String role)
+    {
+        setName(name);
+        setRoles(new String[] { role });
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Object clone() throws CloneNotSupportedException
+    {
+        return super.clone();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     */
+    public void setName(String name)
+    {
+        _name = name;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setRoles(String[] roles)
+    {
+        _roles = roles;
+        _anyRole = false;
+        if (roles != null) 
+            for (int i = roles.length; !_anyRole && i-- > 0;)
+                _anyRole |= ANY_ROLE.equals(roles[i]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if any user role is permitted.
+     */
+    public boolean isAnyRole()
+    {
+        return _anyRole;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return List of roles for this constraint.
+     */
+    public String[] getRoles()
+    {
+        return _roles;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param role
+     * @return True if the constraint contains the role.
+     */
+    public boolean hasRole(String role)
+    {
+        if (_anyRole) return true;
+        if (_roles != null) for (int i = _roles.length; i-- > 0;)
+            if (role.equals(_roles[i])) return true;
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param authenticate True if users must be authenticated
+     */
+    public void setAuthenticate(boolean authenticate)
+    {
+        _authenticate = authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the constraint requires request authentication
+     */
+    public boolean getAuthenticate()
+    {
+        return _authenticate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if authentication required but no roles set
+     */
+    public boolean isForbidden()
+    {
+        return _authenticate && !_anyRole && (_roles == null || _roles.length == 0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param c Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *                2=DC_CONFIDENTIAL
+     */
+    public void setDataConstraint(int c)
+    {
+        if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range");
+        _dataConstraint = c;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL &
+     *         2=DC_CONFIDENTIAL
+     */
+    public int getDataConstraint()
+    {
+        return _dataConstraint;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if a data constraint has been set.
+     */
+    public boolean hasDataConstraint()
+    {
+        return _dataConstraint >= DC_NONE;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return "SC{" + _name
+               + ","
+               + (_anyRole ? "*" : (_roles == null ? "-" : Arrays.asList(_roles).toString()))
+               + ","
+               + (_dataConstraint == DC_UNSET ? "DC_UNSET}" : (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}")));
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/security/Credential.java b/src/java/org/eclipse/jetty/util/security/Credential.java
new file mode 100644
index 0000000..7599bdf
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/Credential.java
@@ -0,0 +1,229 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.Serializable;
+import java.security.MessageDigest;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Credentials. The Credential class represents an abstract mechanism for
+ * checking authentication credentials. A credential instance either represents
+ * a secret, or some data that could only be derived from knowing the secret.
+ * <p>
+ * Often a Credential is related to a Password via a one way algorithm, so while
+ * a Password itself is a Credential, a UnixCrypt or MD5 digest of a a password
+ * is only a credential that can be checked against the password.
+ * <p>
+ * This class includes an implementation for unix Crypt an MD5 digest.
+ * 
+ * @see Password
+ * 
+ */
+public abstract class Credential implements Serializable
+{
+    private static final Logger LOG = Log.getLogger(Credential.class);
+
+    private static final long serialVersionUID = -7760551052768181572L;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check a credential
+     * 
+     * @param credentials The credential to check against. This may either be
+     *                another Credential object, a Password object or a String
+     *                which is interpreted by this credential.
+     * @return True if the credentials indicated that the shared secret is known
+     *         to both this Credential and the passed credential.
+     */
+    public abstract boolean check(Object credentials);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a credential from a String. If the credential String starts with a
+     * known Credential type (eg "CRYPT:" or "MD5:" ) then a Credential of that
+     * type is returned. Else the credential is assumed to be a Password.
+     * 
+     * @param credential String representation of the credential
+     * @return A Credential or Password instance.
+     */
+    public static Credential getCredential(String credential)
+    {
+        if (credential.startsWith(Crypt.__TYPE)) return new Crypt(credential);
+        if (credential.startsWith(MD5.__TYPE)) return new MD5(credential);
+
+        return new Password(credential);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Unix Crypt Credentials
+     */
+    public static class Crypt extends Credential
+    {
+        private static final long serialVersionUID = -2027792997664744210L;
+
+        public static final String __TYPE = "CRYPT:";
+
+        private final String _cooked;
+
+        Crypt(String cooked)
+        {
+            _cooked = cooked.startsWith(Crypt.__TYPE) ? cooked.substring(__TYPE.length()) : cooked;
+        }
+
+        @Override
+        public boolean check(Object credentials)
+        {
+            if (credentials instanceof char[])
+                credentials=new String((char[])credentials);
+            if (!(credentials instanceof String) && !(credentials instanceof Password)) 
+                LOG.warn("Can't check " + credentials.getClass() + " against CRYPT");
+
+            String passwd = credentials.toString();
+            return _cooked.equals(UnixCrypt.crypt(passwd, _cooked));
+        }
+
+        public static String crypt(String user, String pw)
+        {
+            return "CRYPT:" + UnixCrypt.crypt(pw, user);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * MD5 Credentials
+     */
+    public static class MD5 extends Credential
+    {
+        private static final long serialVersionUID = 5533846540822684240L;
+
+        public static final String __TYPE = "MD5:";
+
+        public static final Object __md5Lock = new Object();
+
+        private static MessageDigest __md;
+
+        private final byte[] _digest;
+
+        /* ------------------------------------------------------------ */
+        MD5(String digest)
+        {
+            digest = digest.startsWith(__TYPE) ? digest.substring(__TYPE.length()) : digest;
+            _digest = TypeUtil.parseBytes(digest, 16);
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte[] getDigest()
+        {
+            return _digest;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public boolean check(Object credentials)
+        {
+            try
+            {
+                byte[] digest = null;
+
+                if (credentials instanceof char[])
+                    credentials=new String((char[])credentials);
+                if (credentials instanceof Password || credentials instanceof String)
+                {
+                    synchronized (__md5Lock)
+                    {
+                        if (__md == null) __md = MessageDigest.getInstance("MD5");
+                        __md.reset();
+                        __md.update(credentials.toString().getBytes(StringUtil.__ISO_8859_1));
+                        digest = __md.digest();
+                    }
+                    if (digest == null || digest.length != _digest.length) return false;
+                    for (int i = 0; i < digest.length; i++)
+                        if (digest[i] != _digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof MD5)
+                {
+                    MD5 md5 = (MD5) credentials;
+                    if (_digest.length != md5._digest.length) return false;
+                    for (int i = 0; i < _digest.length; i++)
+                        if (_digest[i] != md5._digest[i]) return false;
+                    return true;
+                }
+                else if (credentials instanceof Credential)
+                {
+                    // Allow credential to attempt check - i.e. this'll work
+                    // for DigestAuthModule$Digest credentials
+                    return ((Credential) credentials).check(this);
+                }
+                else
+                {
+                    LOG.warn("Can't check " + credentials.getClass() + " against MD5");
+                    return false;
+                }
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return false;
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public static String digest(String password)
+        {
+            try
+            {
+                byte[] digest;
+                synchronized (__md5Lock)
+                {
+                    if (__md == null)
+                    {
+                        try
+                        {
+                            __md = MessageDigest.getInstance("MD5");
+                        }
+                        catch (Exception e)
+                        {
+                            LOG.warn(e);
+                            return null;
+                        }
+                    }
+
+                    __md.reset();
+                    __md.update(password.getBytes(StringUtil.__ISO_8859_1));
+                    digest = __md.digest();
+                }
+
+                return __TYPE + TypeUtil.toString(digest, 16);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e);
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/security/Password.java b/src/java/org/eclipse/jetty/util/security/Password.java
new file mode 100644
index 0000000..e0fc5d5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/Password.java
@@ -0,0 +1,257 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.security;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Password utility class.
+ * 
+ * This utility class gets a password or pass phrase either by:
+ * 
+ * <PRE>
+ *  + Password is set as a system property.
+ *  + The password is prompted for and read from standard input
+ *  + A program is run to get the password.
+ * </pre>
+ * 
+ * Passwords that begin with OBF: are de obfuscated. Passwords can be obfuscated
+ * by run org.eclipse.util.Password as a main class. Obfuscated password are
+ * required if a system needs to recover the full password (eg. so that it may
+ * be passed to another system). They are not secure, but prevent casual
+ * observation.
+ * <p>
+ * Passwords that begin with CRYPT: are oneway encrypted with UnixCrypt. The
+ * real password cannot be retrieved, but comparisons can be made to other
+ * passwords. A Crypt can be generated by running org.eclipse.util.UnixCrypt as
+ * a main class, passing password and then the username. Checksum passwords are
+ * a secure(ish) way to store passwords that only need to be checked rather than
+ * recovered. Note that it is not strong security - specially if simple
+ * passwords are used.
+ * 
+ * 
+ */
+public class Password extends Credential
+{
+    private static final Logger LOG = Log.getLogger(Password.class);
+
+    private static final long serialVersionUID = 5062906681431569445L;
+
+    public static final String __OBFUSCATE = "OBF:";
+
+    private String _pw;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     * 
+     * @param password The String password.
+     */
+    public Password(String password)
+    {
+        _pw = password;
+
+        // expand password
+        while (_pw != null && _pw.startsWith(__OBFUSCATE))
+            _pw = deobfuscate(_pw);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _pw;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toStarString()
+    {
+        return "*****************************************************".substring(0, _pw.length());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean check(Object credentials)
+    {
+        if (this == credentials) return true;
+
+        if (credentials instanceof Password) return credentials.equals(_pw);
+
+        if (credentials instanceof String) return credentials.equals(_pw);
+
+        if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials);
+
+        if (credentials instanceof Credential) return ((Credential) credentials).check(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public boolean equals(Object o)
+    {
+        if (this == o) 
+            return true;
+
+        if (null == o) 
+            return false;
+
+        if (o instanceof Password)
+        {
+            Password p = (Password) o;
+            //noinspection StringEquality
+            return p._pw == _pw || (null != _pw && _pw.equals(p._pw));
+        }
+
+        if (o instanceof String) 
+            return o.equals(_pw);
+
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public int hashCode()
+    {
+        return null == _pw ? super.hashCode() : _pw.hashCode();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String obfuscate(String s)
+    {
+        StringBuilder buf = new StringBuilder();
+        byte[] b = s.getBytes();
+
+        buf.append(__OBFUSCATE);
+        for (int i = 0; i < b.length; i++)
+        {
+            byte b1 = b[i];
+            byte b2 = b[s.length() - (i + 1)];
+            int i1 = 127 + b1 + b2;
+            int i2 = 127 + b1 - b2;
+            int i0 = i1 * 256 + i2;
+            String x = Integer.toString(i0, 36);
+
+            switch (x.length())
+            {
+                case 1:
+                    buf.append('0');
+                    buf.append('0');
+                    buf.append('0');
+                    buf.append(x);
+                    break;
+                case 2:
+                    buf.append('0');
+                    buf.append('0');
+                    buf.append(x);
+                    break;
+                case 3:
+                    buf.append('0');
+                    buf.append(x);
+                    break;
+                default:
+                    buf.append(x);
+                    break;
+            }
+        }
+        return buf.toString();
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String deobfuscate(String s)
+    {
+        if (s.startsWith(__OBFUSCATE)) s = s.substring(4);
+
+        byte[] b = new byte[s.length() / 2];
+        int l = 0;
+        for (int i = 0; i < s.length(); i += 4)
+        {
+            String x = s.substring(i, i + 4);
+            int i0 = Integer.parseInt(x, 36);
+            int i1 = (i0 / 256);
+            int i2 = (i0 % 256);
+            b[l++] = (byte) ((i1 + i2 - 254) / 2);
+        }
+
+        return new String(b, 0, l);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a password. A password is obtained by trying
+     * <UL>
+     * <LI>Calling <Code>System.getProperty(realm,dft)</Code>
+     * <LI>Prompting for a password
+     * <LI>Using promptDft if nothing was entered.
+     * </UL>
+     * 
+     * @param realm The realm name for the password, used as a SystemProperty
+     *                name.
+     * @param dft The default password.
+     * @param promptDft The default to use if prompting for the password.
+     * @return Password
+     */
+    public static Password getPassword(String realm, String dft, String promptDft)
+    {
+        String passwd = System.getProperty(realm, dft);
+        if (passwd == null || passwd.length() == 0)
+        {
+            try
+            {
+                System.out.print(realm + ((promptDft != null && promptDft.length() > 0) ? " [dft]" : "") + " : ");
+                System.out.flush();
+                byte[] buf = new byte[512];
+                int len = System.in.read(buf);
+                if (len > 0) passwd = new String(buf, 0, len).trim();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(Log.EXCEPTION, e);
+            }
+            if (passwd == null || passwd.length() == 0) passwd = promptDft;
+        }
+        return new Password(passwd);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param arg
+     */
+    public static void main(String[] arg)
+    {
+        if (arg.length != 1 && arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.jetty.security.Password [<user>] <password>");
+            System.err.println("If the password is ?, the user will be prompted for the password");
+            System.exit(1);
+        }
+        String p = arg[arg.length == 1 ? 0 : 1];
+        Password pw = new Password(p);
+        System.err.println(pw.toString());
+        System.err.println(obfuscate(pw.toString()));
+        System.err.println(Credential.MD5.digest(p));
+        if (arg.length == 2) System.err.println(Credential.Crypt.crypt(arg[0], pw.toString()));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/security/UnixCrypt.java b/src/java/org/eclipse/jetty/util/security/UnixCrypt.java
new file mode 100644
index 0000000..e3f98e8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/security/UnixCrypt.java
@@ -0,0 +1,461 @@
+/*
+ * @(#)UnixCrypt.java	0.9 96/11/25
+ *
+ * Copyright (c) 1996 Aki Yoshida. All rights reserved.
+ *
+ * Permission to use, copy, modify and distribute this software
+ * for non-commercial or commercial purposes and without fee is
+ * hereby granted provided that this copyright notice appears in
+ * all copies.
+ */
+
+/**
+ * Unix crypt(3C) utility
+ *
+ * @version 	0.9, 11/25/96
+ * @author 	Aki Yoshida
+ */
+
+/**
+ * modified April 2001
+ * by Iris Van den Broeke, Daniel Deville
+ */
+
+package org.eclipse.jetty.util.security;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Unix Crypt. Implements the one way cryptography used by Unix systems for
+ * simple password protection.
+ * 
+ * @version $Id: UnixCrypt.java,v 1.1 2005/10/05 14:09:14 janb Exp $
+ * @author Greg Wilkins (gregw)
+ */
+public class UnixCrypt
+{
+
+    /* (mostly) Standard DES Tables from Tom Truscott */
+    private static final byte[] IP = { /* initial permutation */
+    58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1,
+            59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 };
+
+    /* The final permutation is the inverse of IP - no table is necessary */
+    private static final byte[] ExpandTr = { /* expansion operation */
+    32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29,
+            28, 29, 30, 31, 32, 1 };
+
+    private static final byte[] PC1 = { /* permuted choice table 1 */
+    57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36,
+
+    63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 };
+
+    private static final byte[] Rotates = { /* PC1 rotation schedule */
+    1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };
+
+    private static final byte[] PC2 = { /* permuted choice table 2 */
+    9, 18, 14, 17, 11, 24, 1, 5, 22, 25, 3, 28, 15, 6, 21, 10, 35, 38, 23, 19, 12, 4, 26, 8, 43, 54, 16, 7, 27, 20, 13, 2,
+
+    0, 0, 41, 52, 31, 37, 47, 55, 0, 0, 30, 40, 51, 45, 33, 48, 0, 0, 44, 49, 39, 56, 34, 53, 0, 0, 46, 42, 50, 36, 29, 32 };
+
+    private static final byte[][] S = { /* 48->32 bit substitution tables */
+            /* S[1] */
+            { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9,
+             7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
+            /* S[2] */
+            { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12,
+             6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
+            /* S[3] */
+            { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2,
+             12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
+            /* S[4] */
+            { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3,
+             14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
+            /* S[5] */
+            { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12,
+             5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
+            /* S[6] */
+            { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4,
+             10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
+            /* S[7] */
+            { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15,
+             6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
+            /* S[8] */
+            { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10,
+             13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 } };
+
+    private static final byte[] P32Tr = { /* 32-bit permutation function */
+    16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 };
+
+    private static final byte[] CIFP = { /*
+                                             * compressed/interleaved
+                                             * permutation
+                                             */
+    1, 2, 3, 4, 17, 18, 19, 20, 5, 6, 7, 8, 21, 22, 23, 24, 9, 10, 11, 12, 25, 26, 27, 28, 13, 14, 15, 16, 29, 30, 31, 32,
+
+    33, 34, 35, 36, 49, 50, 51, 52, 37, 38, 39, 40, 53, 54, 55, 56, 41, 42, 43, 44, 57, 58, 59, 60, 45, 46, 47, 48, 61, 62, 63, 64 };
+
+    private static final byte[] ITOA64 = { /* 0..63 => ascii-64 */
+    (byte) '.', (byte) '/', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
+            (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M',
+            (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y',
+            (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k',
+            (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w',
+            (byte) 'x', (byte) 'y', (byte) 'z' };
+
+    /* ===== Tables that are initialized at run time ==================== */
+
+    private static final byte[] A64TOI = new byte[128]; /* ascii-64 => 0..63 */
+
+    /* Initial key schedule permutation */
+    private static final long[][] PC1ROT = new long[16][16];
+
+    /* Subsequent key schedule rotation permutations */
+    private static final long[][][] PC2ROT = new long[2][16][16];
+
+    /* Initial permutation/expansion table */
+    private static final long[][] IE3264 = new long[8][16];
+
+    /* Table that combines the S, P, and E operations. */
+    private static final long[][] SPE = new long[8][64];
+
+    /* compressed/interleaved => final permutation table */
+    private static final long[][] CF6464 = new long[16][16];
+
+    /* ==================================== */
+
+    static
+    {
+        byte[] perm = new byte[64];
+        byte[] temp = new byte[64];
+
+        // inverse table.
+        for (int i = 0; i < 64; i++)
+            A64TOI[ITOA64[i]] = (byte) i;
+
+        // PC1ROT - bit reverse, then PC1, then Rotate, then PC2
+        for (int i = 0; i < 64; i++)
+            perm[i] = (byte) 0;
+        
+        for (int i = 0; i < 64; i++)
+        {
+            int k;
+            if ((k = PC2[i]) == 0) continue;
+            k += Rotates[0] - 1;
+            if ((k % 28) < Rotates[0]) k -= 28;
+            k = PC1[k];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[i] = (byte) k;
+        }
+        init_perm(PC1ROT, perm, 8);
+
+        // PC2ROT - PC2 inverse, then Rotate, then PC2
+        for (int j = 0; j < 2; j++)
+        {
+            int k;
+            for (int i = 0; i < 64; i++)
+                perm[i] = temp[i] = 0;
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                temp[k - 1] = (byte) (i + 1);
+            }
+            for (int i = 0; i < 64; i++)
+            {
+                if ((k = PC2[i]) == 0) continue;
+                k += j;
+                if ((k % 28) <= j) k -= 28;
+                perm[i] = temp[k];
+            }
+
+            init_perm(PC2ROT[j], perm, 8);
+        }
+
+        // Bit reverse, intial permupation, expantion
+        for (int i = 0; i < 8; i++)
+        {
+            for (int j = 0; j < 8; j++)
+            {
+                int k = (j < 2) ? 0 : IP[ExpandTr[i * 6 + j - 2] - 1];
+                if (k > 32)
+                    k -= 32;
+                else if (k > 0) k--;
+                if (k > 0)
+                {
+                    k--;
+                    k = (k | 0x07) - (k & 0x07);
+                    k++;
+                }
+                perm[i * 8 + j] = (byte) k;
+            }
+        }
+
+        init_perm(IE3264, perm, 8);
+
+        // Compression, final permutation, bit reverse
+        for (int i = 0; i < 64; i++)
+        {
+            int k = IP[CIFP[i] - 1];
+            if (k > 0)
+            {
+                k--;
+                k = (k | 0x07) - (k & 0x07);
+                k++;
+            }
+            perm[k - 1] = (byte) (i + 1);
+        }
+
+        init_perm(CF6464, perm, 8);
+
+        // SPE table
+        for (int i = 0; i < 48; i++)
+            perm[i] = P32Tr[ExpandTr[i] - 1];
+        for (int t = 0; t < 8; t++)
+        {
+            for (int j = 0; j < 64; j++)
+            {
+                int k = (((j >> 0) & 0x01) << 5) | (((j >> 1) & 0x01) << 3)
+                        | (((j >> 2) & 0x01) << 2)
+                        | (((j >> 3) & 0x01) << 1)
+                        | (((j >> 4) & 0x01) << 0)
+                        | (((j >> 5) & 0x01) << 4);
+                k = S[t][k];
+                k = (((k >> 3) & 0x01) << 0) | (((k >> 2) & 0x01) << 1) | (((k >> 1) & 0x01) << 2) | (((k >> 0) & 0x01) << 3);
+                for (int i = 0; i < 32; i++)
+                    temp[i] = 0;
+                for (int i = 0; i < 4; i++)
+                    temp[4 * t + i] = (byte) ((k >> i) & 0x01);
+                long kk = 0;
+                for (int i = 24; --i >= 0;)
+                    kk = ((kk << 1) | ((long) temp[perm[i] - 1]) << 32 | (temp[perm[i + 24] - 1]));
+
+                SPE[t][j] = to_six_bit(kk);
+            }
+        }
+    }
+
+    /**
+     * You can't call the constructer.
+     */
+    private UnixCrypt()
+    {
+    }
+
+    /**
+     * Returns the transposed and split code of a 24-bit code into a 4-byte
+     * code, each having 6 bits.
+     */
+    private static int to_six_bit(int num)
+    {
+        return (((num << 26) & 0xfc000000) | ((num << 12) & 0xfc0000) | ((num >> 2) & 0xfc00) | ((num >> 16) & 0xfc));
+    }
+
+    /**
+     * Returns the transposed and split code of two 24-bit code into two 4-byte
+     * code, each having 6 bits.
+     */
+    private static long to_six_bit(long num)
+    {
+        return (((num << 26) & 0xfc000000fc000000L) | ((num << 12) & 0xfc000000fc0000L) | ((num >> 2) & 0xfc000000fc00L) | ((num >> 16) & 0xfc000000fcL));
+    }
+
+    /**
+     * Returns the permutation of the given 64-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm6464(long c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 8; --i >= 0;)
+        {
+            int t = (int) (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the permutation of the given 32-bit code with the specified
+     * permutataion table.
+     */
+    private static long perm3264(int c, long[][] p)
+    {
+        long out = 0L;
+        for (int i = 4; --i >= 0;)
+        {
+            int t = (0x00ff & c);
+            c >>= 8;
+            long tp = p[i << 1][t & 0x0f];
+            out |= tp;
+            tp = p[(i << 1) + 1][t >> 4];
+            out |= tp;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the key schedule for the given key.
+     */
+    private static long[] des_setkey(long keyword)
+    {
+        long K = perm6464(keyword, PC1ROT);
+        long[] KS = new long[16];
+        KS[0] = K & ~0x0303030300000000L;
+
+        for (int i = 1; i < 16; i++)
+        {
+            KS[i] = K;
+            K = perm6464(K, PC2ROT[Rotates[i] - 1]);
+
+            KS[i] = K & ~0x0303030300000000L;
+        }
+        return KS;
+    }
+
+    /**
+     * Returns the DES encrypted code of the given word with the specified
+     * environment.
+     */
+    private static long des_cipher(long in, int salt, int num_iter, long[] KS)
+    {
+        salt = to_six_bit(salt);
+        long L = in;
+        long R = L;
+        L &= 0x5555555555555555L;
+        R = (R & 0xaaaaaaaa00000000L) | ((R >> 1) & 0x0000000055555555L);
+        L = ((((L << 1) | (L << 32)) & 0xffffffff00000000L) | ((R | (R >> 32)) & 0x00000000ffffffffL));
+
+        L = perm3264((int) (L >> 32), IE3264);
+        R = perm3264((int) (L & 0xffffffff), IE3264);
+
+        while (--num_iter >= 0)
+        {
+            for (int loop_count = 0; loop_count < 8; loop_count++)
+            {
+                long kp;
+                long B;
+                long k;
+
+                kp = KS[(loop_count << 1)];
+                k = ((R >> 32) ^ R) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ R ^ kp);
+
+                L ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+
+                kp = KS[(loop_count << 1) + 1];
+                k = ((L >> 32) ^ L) & salt & 0xffffffffL;
+                k |= (k << 32);
+                B = (k ^ L ^ kp);
+
+                R ^= (SPE[0][(int) ((B >> 58) & 0x3f)] ^ SPE[1][(int) ((B >> 50) & 0x3f)]
+                      ^ SPE[2][(int) ((B >> 42) & 0x3f)]
+                      ^ SPE[3][(int) ((B >> 34) & 0x3f)]
+                      ^ SPE[4][(int) ((B >> 26) & 0x3f)]
+                      ^ SPE[5][(int) ((B >> 18) & 0x3f)]
+                      ^ SPE[6][(int) ((B >> 10) & 0x3f)] ^ SPE[7][(int) ((B >> 2) & 0x3f)]);
+            }
+            // swap L and R
+            L ^= R;
+            R ^= L;
+            L ^= R;
+        }
+        L = ((((L >> 35) & 0x0f0f0f0fL) | (((L & 0xffffffff) << 1) & 0xf0f0f0f0L)) << 32 | (((R >> 35) & 0x0f0f0f0fL) | (((R & 0xffffffff) << 1) & 0xf0f0f0f0L)));
+
+        L = perm6464(L, CF6464);
+
+        return L;
+    }
+
+    /**
+     * Initializes the given permutation table with the mapping table.
+     */
+    private static void init_perm(long[][] perm, byte[] p, int chars_out)
+    {
+        for (int k = 0; k < chars_out * 8; k++)
+        {
+
+            int l = p[k] - 1;
+            if (l < 0) continue;
+            int i = l >> 2;
+            l = 1 << (l & 0x03);
+            for (int j = 0; j < 16; j++)
+            {
+                int s = ((k & 0x07) + ((7 - (k >> 3)) << 3));
+                if ((j & l) != 0x00) perm[i][j] |= (1L << s);
+            }
+        }
+    }
+
+    /**
+     * Encrypts String into crypt (Unix) code.
+     * 
+     * @param key the key to be encrypted
+     * @param setting the salt to be used
+     * @return the encrypted String
+     */
+    public static String crypt(String key, String setting)
+    {
+        long constdatablock = 0L; /* encryption constant */
+        byte[] cryptresult = new byte[13]; /* encrypted result */
+        long keyword = 0L;
+        /* invalid parameters! */
+        if (key == null || setting == null) return "*"; // will NOT match under
+        // ANY circumstances!
+
+        int keylen = key.length();
+
+        for (int i = 0; i < 8; i++)
+        {
+            keyword = (keyword << 8) | ((i < keylen) ? 2 * key.charAt(i) : 0);
+        }
+
+        long[] KS = des_setkey(keyword);
+
+        int salt = 0;
+        for (int i = 2; --i >= 0;)
+        {
+            char c = (i < setting.length()) ? setting.charAt(i) : '.';
+            cryptresult[i] = (byte) c;
+            salt = (salt << 6) | (0x00ff & A64TOI[c]);
+        }
+
+        long rsltblock = des_cipher(constdatablock, salt, 25, KS);
+
+        cryptresult[12] = ITOA64[(((int) rsltblock) << 2) & 0x3f];
+        rsltblock >>= 4;
+        for (int i = 12; --i >= 2;)
+        {
+            cryptresult[i] = ITOA64[((int) rsltblock) & 0x3f];
+            rsltblock >>= 6;
+        }
+
+        return new String(cryptresult, 0, 13);
+    }
+
+    public static void main(String[] arg)
+    {
+        if (arg.length != 2)
+        {
+            System.err.println("Usage - java org.eclipse.util.UnixCrypt <key> <salt>");
+            System.exit(1);
+        }
+
+        System.err.println("Crypt=" + crypt(arg[0], arg[1]));
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java b/src/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
new file mode 100644
index 0000000..0eea9ab
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ssl/AliasedX509ExtendedKeyManager.java
@@ -0,0 +1,130 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509ExtendedKeyManager extends X509ExtendedKeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509ExtendedKeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {   
+        return _keyAlias == null ? _keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineServerAlias(java.lang.String, java.security.Principal[], javax.net.ssl.SSLEngine)
+     */
+    @Override
+    public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineServerAlias(keyType,issuers,engine) : _keyAlias;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509ExtendedKeyManager#chooseEngineClientAlias(String[], Principal[], SSLEngine)
+     */
+    @Override
+    public String chooseEngineClientAlias(String keyType[], Principal[] issuers, SSLEngine engine)
+    {
+        return _keyAlias == null ? super.chooseEngineClientAlias(keyType,issuers,engine) : _keyAlias;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java b/src/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
new file mode 100644
index 0000000..5da3408
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ssl/AliasedX509KeyManager.java
@@ -0,0 +1,107 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509KeyManager;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * KeyManager to select a key with desired alias
+ * while delegating processing to specified KeyManager
+ * Can be used both with server and client sockets
+ */
+public class AliasedX509KeyManager implements X509KeyManager
+{
+    private String _keyAlias;
+    private X509KeyManager _keyManager;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct KeyManager instance
+     * @param keyAlias Alias of the key to be selected
+     * @param keyManager Instance of KeyManager to be wrapped
+     * @throws Exception
+     */
+    public AliasedX509KeyManager(String keyAlias, X509KeyManager keyManager) throws Exception
+    {
+        _keyAlias = keyAlias;
+        _keyManager = keyManager;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseClientAlias(java.lang.String[], java.security.Principal[], java.net.Socket)
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)
+    {
+        return _keyAlias == null ? _keyManager.chooseClientAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#chooseServerAlias(java.lang.String, java.security.Principal[], java.net.Socket)
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
+    {   
+        return _keyAlias == null ?_keyManager.chooseServerAlias(keyType, issuers, socket) : _keyAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getClientAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getClientAliases(keyType, issuers);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getServerAliases(java.lang.String, java.security.Principal[])
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers)
+    {
+        return _keyManager.getServerAliases(keyType, issuers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getCertificateChain(java.lang.String)
+     */
+    public X509Certificate[] getCertificateChain(String alias)
+    {
+        return _keyManager.getCertificateChain(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.net.ssl.X509KeyManager#getPrivateKey(java.lang.String)
+     */
+    public PrivateKey getPrivateKey(String alias)
+    {
+        return _keyManager.getPrivateKey(alias);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/src/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
new file mode 100644
index 0000000..c304f17
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/ssl/SslContextFactory.java
@@ -0,0 +1,1537 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.ssl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.security.InvalidParameterException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.CRL;
+import java.security.cert.CertStore;
+import java.security.cert.Certificate;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.CertificateUtils;
+import org.eclipse.jetty.util.security.CertificateValidator;
+import org.eclipse.jetty.util.security.Password;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SslContextFactory is used to configure SSL connectors
+ * as well as HttpClient. It holds all SSL parameters and
+ * creates SSL context based on these parameters to be
+ * used by the SSL connectors.
+ */
+public class SslContextFactory extends AbstractLifeCycle
+{
+    public final static TrustManager[] TRUST_ALL_CERTS = new X509TrustManager[]{new X509TrustManager()
+    {
+        public java.security.cert.X509Certificate[] getAcceptedIssuers()
+        {
+            return new java.security.cert.X509Certificate[]{};
+        }
+
+        public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+
+        public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
+        {
+        }
+    }};
+
+    private static final Logger LOG = Log.getLogger(SslContextFactory.class);
+
+    public static final String DEFAULT_KEYMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.KeyManagerFactory.algorithm") == null ?
+                "SunX509" : Security.getProperty("ssl.KeyManagerFactory.algorithm"));
+    public static final String DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM =
+        (Security.getProperty("ssl.TrustManagerFactory.algorithm") == null ?
+                "SunX509" : Security.getProperty("ssl.TrustManagerFactory.algorithm"));
+
+    /** Default value for the keystore location path. */
+    public static final String DEFAULT_KEYSTORE_PATH =
+        System.getProperty("user.home") + File.separator + ".keystore";
+
+    /** String name of key password property. */
+    public static final String KEYPASSWORD_PROPERTY = "org.eclipse.jetty.ssl.keypassword";
+
+    /** String name of keystore password property. */
+    public static final String PASSWORD_PROPERTY = "org.eclipse.jetty.ssl.password";
+
+    /** Excluded protocols. */
+    private final Set<String> _excludeProtocols = new LinkedHashSet<String>();
+    /** Included protocols. */
+    private Set<String> _includeProtocols = null;
+
+    /** Excluded cipher suites. */
+    private final Set<String> _excludeCipherSuites = new LinkedHashSet<String>();
+    /** Included cipher suites. */
+    private Set<String> _includeCipherSuites = null;
+
+    /** Keystore path. */
+    private String _keyStorePath;
+    /** Keystore provider name */
+    private String _keyStoreProvider;
+    /** Keystore type */
+    private String _keyStoreType = "JKS";
+    /** Keystore input stream */
+    private InputStream _keyStoreInputStream;
+
+    /** SSL certificate alias */
+    private String _certAlias;
+
+    /** Truststore path */
+    private String _trustStorePath;
+    /** Truststore provider name */
+    private String _trustStoreProvider;
+    /** Truststore type */
+    private String _trustStoreType = "JKS";
+    /** Truststore input stream */
+    private InputStream _trustStoreInputStream;
+
+    /** Set to true if client certificate authentication is required */
+    private boolean _needClientAuth = false;
+    /** Set to true if client certificate authentication is desired */
+    private boolean _wantClientAuth = false;
+
+    /** Set to true if renegotiation is allowed */
+    private boolean _allowRenegotiate = true;
+
+    /** Keystore password */
+    private transient Password _keyStorePassword;
+    /** Key manager password */
+    private transient Password _keyManagerPassword;
+    /** Truststore password */
+    private transient Password _trustStorePassword;
+
+    /** SSL provider name */
+    private String _sslProvider;
+    /** SSL protocol name */
+    private String _sslProtocol = "TLS";
+
+    /** SecureRandom algorithm */
+    private String _secureRandomAlgorithm;
+    /** KeyManager factory algorithm */
+    private String _keyManagerFactoryAlgorithm = DEFAULT_KEYMANAGERFACTORY_ALGORITHM;
+    /** TrustManager factory algorithm */
+    private String _trustManagerFactoryAlgorithm = DEFAULT_TRUSTMANAGERFACTORY_ALGORITHM;
+
+    /** Set to true if SSL certificate validation is required */
+    private boolean _validateCerts;
+    /** Set to true if SSL certificate of the peer validation is required */
+    private boolean _validatePeerCerts;
+    /** Maximum certification path length (n - number of intermediate certs, -1 for unlimited) */
+    private int _maxCertPathLength = -1;
+    /** Path to file that contains Certificate Revocation List */
+    private String _crlPath;
+    /** Set to true to enable CRL Distribution Points (CRLDP) support */
+    private boolean _enableCRLDP = false;
+    /** Set to true to enable On-Line Certificate Status Protocol (OCSP) support */
+    private boolean _enableOCSP = false;
+    /** Location of OCSP Responder */
+    private String _ocspResponderURL;
+
+    /** SSL keystore */
+    private KeyStore _keyStore;
+    /** SSL truststore */
+    private KeyStore _trustStore;
+    /** Set to true to enable SSL Session caching */
+    private boolean _sessionCachingEnabled = true;
+    /** SSL session cache size */
+    private int _sslSessionCacheSize;
+    /** SSL session timeout */
+    private int _sslSessionTimeout;
+
+    /** SSL context */
+    private SSLContext _context;
+
+    private boolean _trustAll;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     */
+    public SslContextFactory()
+    {
+        _trustAll=true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct an instance of SslContextFactory
+     * Default constructor for use in XmlConfiguration files
+     * @param trustAll whether to blindly trust all certificates
+     * @see #setTrustAll(boolean)
+     */
+    public SslContextFactory(boolean trustAll)
+    {
+        _trustAll=trustAll;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct an instance of SslContextFactory
+     * @param keyStorePath default keystore location
+     */
+    public SslContextFactory(String keyStorePath)
+    {
+        _keyStorePath = keyStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Create the SSLContext object and start the lifecycle
+     * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        if (_context == null)
+        {
+            if (_keyStore==null && _keyStoreInputStream == null && _keyStorePath == null &&
+                _trustStore==null && _trustStoreInputStream == null && _trustStorePath == null )
+            {
+                TrustManager[] trust_managers=null;
+
+                if (_trustAll)
+                {
+                    LOG.debug("No keystore or trust store configured.  ACCEPTING UNTRUSTED CERTIFICATES!!!!!");
+                    // Create a trust manager that does not validate certificate chains
+                    trust_managers = TRUST_ALL_CERTS;
+                }
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                _context = SSLContext.getInstance(_sslProtocol);
+                _context.init(null, trust_managers, secureRandom);
+            }
+            else
+            {
+                // verify that keystore and truststore
+                // parameters are set up correctly
+                checkKeyStore();
+
+                KeyStore keyStore = loadKeyStore();
+                KeyStore trustStore = loadTrustStore();
+
+                Collection<? extends CRL> crls = loadCRL(_crlPath);
+
+                if (_validateCerts && keyStore != null)
+                {
+                    if (_certAlias == null)
+                    {
+                        List<String> aliases = Collections.list(keyStore.aliases());
+                        _certAlias = aliases.size() == 1 ? aliases.get(0) : null;
+                    }
+
+                    Certificate cert = _certAlias == null?null:keyStore.getCertificate(_certAlias);
+                    if (cert == null)
+                    {
+                        throw new Exception("No certificate found in the keystore" + (_certAlias==null ? "":" for alias " + _certAlias));
+                    }
+
+                    CertificateValidator validator = new CertificateValidator(trustStore, crls);
+                    validator.setMaxCertPathLength(_maxCertPathLength);
+                    validator.setEnableCRLDP(_enableCRLDP);
+                    validator.setEnableOCSP(_enableOCSP);
+                    validator.setOcspResponderURL(_ocspResponderURL);
+                    validator.validate(keyStore, cert);
+                }
+
+                KeyManager[] keyManagers = getKeyManagers(keyStore);
+                TrustManager[] trustManagers = getTrustManagers(trustStore,crls);
+
+                SecureRandom secureRandom = (_secureRandomAlgorithm == null)?null:SecureRandom.getInstance(_secureRandomAlgorithm);
+                _context = (_sslProvider == null)?SSLContext.getInstance(_sslProtocol):SSLContext.getInstance(_sslProtocol,_sslProvider);
+                _context.init(keyManagers,trustManagers,secureRandom);
+
+                SSLEngine engine=newSslEngine();
+
+                LOG.info("Enabled Protocols {} of {}",Arrays.asList(engine.getEnabledProtocols()),Arrays.asList(engine.getSupportedProtocols()));
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Enabled Ciphers   {} of {}",Arrays.asList(engine.getEnabledCipherSuites()),Arrays.asList(engine.getSupportedCipherSuites()));
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of protocol names to exclude from
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getExcludeProtocols()
+    {
+        return _excludeProtocols.toArray(new String[_excludeProtocols.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocols
+     *            The array of protocol names to exclude from
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setExcludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+
+        _excludeProtocols.clear();
+        _excludeProtocols.addAll(Arrays.asList(protocols));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocol Protocol names to add to {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void addExcludeProtocols(String... protocol)
+    {
+        checkNotStarted();
+        _excludeProtocols.addAll(Arrays.asList(protocol));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of protocol names to include in
+     * {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public String[] getIncludeProtocols()
+    {
+        return _includeProtocols.toArray(new String[_includeProtocols.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocols
+     *            The array of protocol names to include in
+     *            {@link SSLEngine#setEnabledProtocols(String[])}
+     */
+    public void setIncludeProtocols(String... protocols)
+    {
+        checkNotStarted();
+
+        _includeProtocols = new LinkedHashSet<String>(Arrays.asList(protocols));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of cipher suite names to exclude from
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getExcludeCipherSuites()
+    {
+        return _excludeCipherSuites.toArray(new String[_excludeCipherSuites.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cipherSuites
+     *            The array of cipher suite names to exclude from
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setExcludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.clear();
+        _excludeCipherSuites.addAll(Arrays.asList(cipherSuites));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cipher Cipher names to add to {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void addExcludeCipherSuites(String... cipher)
+    {
+        checkNotStarted();
+        _excludeCipherSuites.addAll(Arrays.asList(cipher));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The array of cipher suite names to include in
+     * {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public String[] getIncludeCipherSuites()
+    {
+        return _includeCipherSuites.toArray(new String[_includeCipherSuites.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param cipherSuites
+     *            The array of cipher suite names to include in
+     *            {@link SSLEngine#setEnabledCipherSuites(String[])}
+     */
+    public void setIncludeCipherSuites(String... cipherSuites)
+    {
+        checkNotStarted();
+
+        _includeCipherSuites = new LinkedHashSet<String>(Arrays.asList(cipherSuites));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file or URL of the SSL Key store.
+     */
+    public String getKeyStorePath()
+    {
+        return _keyStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Deprecated
+    public String getKeyStore()
+    {
+        return _keyStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keyStorePath
+     *            The file or URL of the SSL Key store.
+     */
+    public void setKeyStorePath(String keyStorePath)
+    {
+        checkNotStarted();
+
+        _keyStorePath = keyStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keyStorePath the file system path or URL of the keystore
+     * @deprecated Use {@link #setKeyStorePath(String)}
+     */
+    @Deprecated
+    public void setKeyStore(String keyStorePath)
+    {
+        checkNotStarted();
+
+        _keyStorePath = keyStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The provider of the key store
+     */
+    public String getKeyStoreProvider()
+    {
+        return _keyStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keyStoreProvider
+     *            The provider of the key store
+     */
+    public void setKeyStoreProvider(String keyStoreProvider)
+    {
+        checkNotStarted();
+
+        _keyStoreProvider = keyStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the key store (default "JKS")
+     */
+    public String getKeyStoreType()
+    {
+        return (_keyStoreType);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param keyStoreType
+     *            The type of the key store (default "JKS")
+     */
+    public void setKeyStoreType(String keyStoreType)
+    {
+        checkNotStarted();
+
+        _keyStoreType = keyStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the _keyStoreInputStream.
+     * @return the _keyStoreInputStream
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public InputStream getKeyStoreInputStream()
+    {
+        checkKeyStore();
+
+        return _keyStoreInputStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the keyStoreInputStream.
+     * @param keyStoreInputStream the InputStream to the KeyStore
+     *
+     * @deprecated Use {@link #setKeyStore(KeyStore)}
+     */
+    @Deprecated
+    public void setKeyStoreInputStream(InputStream keyStoreInputStream)
+    {
+        checkNotStarted();
+
+        _keyStoreInputStream = keyStoreInputStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Alias of SSL certificate for the connector
+     */
+    public String getCertAlias()
+    {
+        return _certAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param certAlias
+     *            Alias of SSL certificate for the connector
+     */
+    public void setCertAlias(String certAlias)
+    {
+        checkNotStarted();
+
+        _certAlias = certAlias;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The file name or URL of the trust store location
+     */
+    public String getTrustStore()
+    {
+        return _trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStorePath
+     *            The file name or URL of the trust store location
+     */
+    public void setTrustStore(String trustStorePath)
+    {
+        checkNotStarted();
+
+        _trustStorePath = trustStorePath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The provider of the trust store
+     */
+    public String getTrustStoreProvider()
+    {
+        return _trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreProvider
+     *            The provider of the trust store
+     */
+    public void setTrustStoreProvider(String trustStoreProvider)
+    {
+        checkNotStarted();
+
+        _trustStoreProvider = trustStoreProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The type of the trust store (default "JKS")
+     */
+    public String getTrustStoreType()
+    {
+        return _trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustStoreType
+     *            The type of the trust store (default "JKS")
+     */
+    public void setTrustStoreType(String trustStoreType)
+    {
+        checkNotStarted();
+
+        _trustStoreType = trustStoreType;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the _trustStoreInputStream.
+     * @return the _trustStoreInputStream
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public InputStream getTrustStoreInputStream()
+    {
+        checkKeyStore();
+
+        return _trustStoreInputStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the _trustStoreInputStream.
+     * @param trustStoreInputStream the InputStream to the TrustStore
+     *
+     * @deprecated
+     */
+    @Deprecated
+    public void setTrustStoreInputStream(InputStream trustStoreInputStream)
+    {
+        checkNotStarted();
+
+        _trustStoreInputStream = trustStoreInputStream;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public boolean getNeedClientAuth()
+    {
+        return _needClientAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param needClientAuth
+     *            True if SSL needs client authentication.
+     * @see SSLEngine#getNeedClientAuth()
+     */
+    public void setNeedClientAuth(boolean needClientAuth)
+    {
+        checkNotStarted();
+
+        _needClientAuth = needClientAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public boolean getWantClientAuth()
+    {
+        return _wantClientAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param wantClientAuth
+     *            True if SSL wants client authentication.
+     * @see SSLEngine#getWantClientAuth()
+     */
+    public void setWantClientAuth(boolean wantClientAuth)
+    {
+        checkNotStarted();
+
+        _wantClientAuth = wantClientAuth;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificate has to be validated
+     * @deprecated
+     */
+    @Deprecated
+    public boolean getValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificate has to be validated
+     */
+    public boolean isValidateCerts()
+    {
+        return _validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param validateCerts
+     *            true if SSL certificates have to be validated
+     */
+    public void setValidateCerts(boolean validateCerts)
+    {
+        checkNotStarted();
+
+        _validateCerts = validateCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if SSL certificates of the peer have to be validated
+     */
+    public boolean isValidatePeerCerts()
+    {
+        return _validatePeerCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param validatePeerCerts
+     *            true if SSL certificates of the peer have to be validated
+     */
+    public void setValidatePeerCerts(boolean validatePeerCerts)
+    {
+        checkNotStarted();
+
+        _validatePeerCerts = validatePeerCerts;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if SSL re-negotiation is allowed (default false)
+     */
+    public boolean isAllowRenegotiate()
+    {
+        return _allowRenegotiate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set if SSL re-negotiation is allowed. CVE-2009-3555 discovered
+     * a vulnerability in SSL/TLS with re-negotiation.  If your JVM
+     * does not have CVE-2009-3555 fixed, then re-negotiation should
+     * not be allowed.  CVE-2009-3555 was fixed in Sun java 1.6 with a ban
+     * of renegotiates in u19 and with RFC5746 in u22.
+     *
+     * @param allowRenegotiate
+     *            true if re-negotiation is allowed (default false)
+     */
+    public void setAllowRenegotiate(boolean allowRenegotiate)
+    {
+        checkNotStarted();
+
+        _allowRenegotiate = allowRenegotiate;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password for the key store
+     */
+    public void setKeyStorePassword(String password)
+    {
+        checkNotStarted();
+
+        _keyStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password (if any) for the specific key within the key store
+     */
+    public void setKeyManagerPassword(String password)
+    {
+        checkNotStarted();
+
+        _keyManagerPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param password
+     *            The password for the trust store
+     */
+    public void setTrustStorePassword(String password)
+    {
+        checkNotStarted();
+
+        _trustStorePassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSL provider name, which if set is passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProvider()
+    {
+        return _sslProvider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param provider
+     *            The SSL provider name, which if set is passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProvider(String provider)
+    {
+        checkNotStarted();
+
+        _sslProvider = provider;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSL protocol (default "TLS") passed to
+     * {@link SSLContext#getInstance(String, String)}
+     */
+    public String getProtocol()
+    {
+        return _sslProtocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocol
+     *            The SSL protocol (default "TLS") passed to
+     *            {@link SSLContext#getInstance(String, String)}
+     */
+    public void setProtocol(String protocol)
+    {
+        checkNotStarted();
+
+        _sslProtocol = protocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name, which if set is passed to
+     * {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     * {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public String getSecureRandomAlgorithm()
+    {
+        return _secureRandomAlgorithm;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm
+     *            The algorithm name, which if set is passed to
+     *            {@link SecureRandom#getInstance(String)} to obtain the {@link SecureRandom} instance passed to
+     *            {@link SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], SecureRandom)}
+     */
+    public void setSecureRandomAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+
+        _secureRandomAlgorithm = algorithm;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public String getSslKeyManagerFactoryAlgorithm()
+    {
+        return (_keyManagerFactoryAlgorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link KeyManagerFactory}
+     */
+    public void setSslKeyManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+
+        _keyManagerFactoryAlgorithm = algorithm;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     */
+    public String getTrustManagerFactoryAlgorithm()
+    {
+        return (_trustManagerFactoryAlgorithm);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public boolean isTrustAll()
+    {
+        return _trustAll;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param trustAll True if all certificates should be trusted if there is no KeyStore or TrustStore
+     */
+    public void setTrustAll(boolean trustAll)
+    {
+        _trustAll = trustAll;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param algorithm
+     *            The algorithm name (default "SunX509") used by the {@link TrustManagerFactory}
+     *            Use the string "TrustAll" to install a trust manager that trusts all.
+     */
+    public void setTrustManagerFactoryAlgorithm(String algorithm)
+    {
+        checkNotStarted();
+
+        _trustManagerFactoryAlgorithm = algorithm;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Path to file that contains Certificate Revocation List
+     */
+    public String getCrlPath()
+    {
+        return _crlPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param crlPath
+     *            Path to file that contains Certificate Revocation List
+     */
+    public void setCrlPath(String crlPath)
+    {
+        checkNotStarted();
+
+        _crlPath = crlPath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Maximum number of intermediate certificates in
+     * the certification path (-1 for unlimited)
+     */
+    public int getMaxCertPathLength()
+    {
+        return _maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxCertPathLength
+     *            maximum number of intermediate certificates in
+     *            the certification path (-1 for unlimited)
+     */
+    public void setMaxCertPathLength(int maxCertPathLength)
+    {
+        checkNotStarted();
+
+        _maxCertPathLength = maxCertPathLength;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The SSLContext
+     */
+    public SSLContext getSslContext()
+    {
+        if (!isStarted())
+            throw new IllegalStateException(getState());
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param sslContext
+     *            Set a preconfigured SSLContext
+     */
+    public void setSslContext(SSLContext sslContext)
+    {
+        checkNotStarted();
+
+        _context = sslContext;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Override this method to provide alternate way to load a keystore.
+     *
+     * @return the key store instance
+     * @throws Exception if the keystore cannot be loaded
+     */
+    protected KeyStore loadKeyStore() throws Exception
+    {
+        return _keyStore != null ? _keyStore : getKeyStore(_keyStoreInputStream,
+                _keyStorePath, _keyStoreType, _keyStoreProvider,
+                _keyStorePassword==null? null: _keyStorePassword.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Override this method to provide alternate way to load a truststore.
+     *
+     * @return the key store instance
+     * @throws Exception if the truststore cannot be loaded
+     */
+    protected KeyStore loadTrustStore() throws Exception
+    {
+        return _trustStore != null ? _trustStore : getKeyStore(_trustStoreInputStream,
+                _trustStorePath, _trustStoreType,  _trustStoreProvider,
+                _trustStorePassword==null? null: _trustStorePassword.toString());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads keystore using an input stream or a file path in the same
+     * order of precedence.
+     *
+     * Required for integrations to be able to override the mechanism
+     * used to load a keystore in order to provide their own implementation.
+     *
+     * @param storeStream keystore input stream
+     * @param storePath path of keystore file
+     * @param storeType keystore type
+     * @param storeProvider keystore provider
+     * @param storePassword keystore password
+     * @return created keystore
+     * @throws Exception if the keystore cannot be obtained
+     *
+     * @deprecated
+     */
+    @Deprecated
+    protected KeyStore getKeyStore(InputStream storeStream, String storePath, String storeType, String storeProvider, String storePassword) throws Exception
+    {
+        return CertificateUtils.getKeyStore(storeStream, storePath, storeType, storeProvider, storePassword);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Loads certificate revocation list (CRL) from a file.
+     *
+     * Required for integrations to be able to override the mechanism used to
+     * load CRL in order to provide their own implementation.
+     *
+     * @param crlPath path of certificate revocation list file
+     * @return Collection of CRL's
+     * @throws Exception if the certificate revocation list cannot be loaded
+     */
+    protected Collection<? extends CRL> loadCRL(String crlPath) throws Exception
+    {
+        return CertificateUtils.loadCRL(crlPath);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected KeyManager[] getKeyManagers(KeyStore keyStore) throws Exception
+    {
+        KeyManager[] managers = null;
+
+        if (keyStore != null)
+        {
+            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_keyManagerFactoryAlgorithm);
+            keyManagerFactory.init(keyStore,_keyManagerPassword == null?(_keyStorePassword == null?null:_keyStorePassword.toString().toCharArray()):_keyManagerPassword.toString().toCharArray());
+            managers = keyManagerFactory.getKeyManagers();
+
+            if (_certAlias != null)
+            {
+                for (int idx = 0; idx < managers.length; idx++)
+                {
+                    if (managers[idx] instanceof X509KeyManager)
+                    {
+                        managers[idx] = new AliasedX509ExtendedKeyManager(_certAlias,(X509KeyManager)managers[idx]);
+                    }
+                }
+            }
+        }
+
+        return managers;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
+    {
+        TrustManager[] managers = null;
+        if (trustStore != null)
+        {
+            // Revocation checking is only supported for PKIX algorithm
+            if (_validatePeerCerts && _trustManagerFactoryAlgorithm.equalsIgnoreCase("PKIX"))
+            {
+                PKIXBuilderParameters pbParams = new PKIXBuilderParameters(trustStore,new X509CertSelector());
+
+                // Set maximum certification path length
+                pbParams.setMaxPathLength(_maxCertPathLength);
+
+                // Make sure revocation checking is enabled
+                pbParams.setRevocationEnabled(true);
+
+                if (crls != null && !crls.isEmpty())
+                {
+                    pbParams.addCertStore(CertStore.getInstance("Collection",new CollectionCertStoreParameters(crls)));
+                }
+
+                if (_enableCRLDP)
+                {
+                    // Enable Certificate Revocation List Distribution Points (CRLDP) support
+                    System.setProperty("com.sun.security.enableCRLDP","true");
+                }
+
+                if (_enableOCSP)
+                {
+                    // Enable On-Line Certificate Status Protocol (OCSP) support
+                    Security.setProperty("ocsp.enable","true");
+
+                    if (_ocspResponderURL != null)
+                    {
+                        // Override location of OCSP Responder
+                        Security.setProperty("ocsp.responderURL", _ocspResponderURL);
+                    }
+                }
+
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(new CertPathTrustManagerParameters(pbParams));
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+            else
+            {
+                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_trustManagerFactoryAlgorithm);
+                trustManagerFactory.init(trustStore);
+
+                managers = trustManagerFactory.getTrustManagers();
+            }
+        }
+
+        return managers;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check KeyStore Configuration. Ensures that if keystore has been
+     * configured but there's no truststore, that keystore is
+     * used as truststore.
+     * @throws IllegalStateException if SslContextFactory configuration can't be used.
+     */
+    public void checkKeyStore()
+    {
+        if (_context != null)
+            return; //nothing to check if using preconfigured context
+
+
+        if (_keyStore == null && _keyStoreInputStream == null && _keyStorePath == null)
+            throw new IllegalStateException("SSL doesn't have a valid keystore");
+
+        // if the keystore has been configured but there is no
+        // truststore configured, use the keystore as the truststore
+        if (_trustStore == null && _trustStoreInputStream == null && _trustStorePath == null)
+        {
+            _trustStore = _keyStore;
+            _trustStorePath = _keyStorePath;
+            _trustStoreInputStream = _keyStoreInputStream;
+            _trustStoreType = _keyStoreType;
+            _trustStoreProvider = _keyStoreProvider;
+            _trustStorePassword = _keyStorePassword;
+            _trustManagerFactoryAlgorithm = _keyManagerFactoryAlgorithm;
+        }
+
+        // It's the same stream we cannot read it twice, so read it once in memory
+        if (_keyStoreInputStream != null && _keyStoreInputStream == _trustStoreInputStream)
+        {
+            try
+            {
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                IO.copy(_keyStoreInputStream, baos);
+                _keyStoreInputStream.close();
+
+                _keyStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+                _trustStoreInputStream = new ByteArrayInputStream(baos.toByteArray());
+            }
+            catch (Exception ex)
+            {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Select protocols to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported protocols.
+     * @param enabledProtocols Array of enabled protocols
+     * @param supportedProtocols Array of supported protocols
+     * @return Array of protocols to enable
+     */
+    public String[] selectProtocols(String[] enabledProtocols, String[] supportedProtocols)
+    {
+        Set<String> selected_protocols = new LinkedHashSet<String>();
+
+        // Set the starting protocols - either from the included or enabled list
+        if (_includeProtocols!=null)
+        {
+            // Use only the supported included protocols
+            for (String protocol : _includeProtocols)
+                if(Arrays.asList(supportedProtocols).contains(protocol))
+                    selected_protocols.add(protocol);
+        }
+        else
+            selected_protocols.addAll(Arrays.asList(enabledProtocols));
+
+
+        // Remove any excluded protocols
+        if (_excludeProtocols != null)
+            selected_protocols.removeAll(_excludeProtocols);
+
+        return selected_protocols.toArray(new String[selected_protocols.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Select cipher suites to be used by the connector
+     * based on configured inclusion and exclusion lists
+     * as well as enabled and supported cipher suite lists.
+     * @param enabledCipherSuites Array of enabled cipher suites
+     * @param supportedCipherSuites Array of supported cipher suites
+     * @return Array of cipher suites to enable
+     */
+    public String[] selectCipherSuites(String[] enabledCipherSuites, String[] supportedCipherSuites)
+    {
+        Set<String> selected_ciphers = new LinkedHashSet<String>();
+
+        // Set the starting ciphers - either from the included or enabled list
+        if (_includeCipherSuites!=null)
+        {
+            // Use only the supported included ciphers
+            for (String cipherSuite : _includeCipherSuites)
+                if(Arrays.asList(supportedCipherSuites).contains(cipherSuite))
+                    selected_ciphers.add(cipherSuite);
+        }
+        else
+            selected_ciphers.addAll(Arrays.asList(enabledCipherSuites));
+
+
+        // Remove any excluded ciphers
+        if (_excludeCipherSuites != null)
+            selected_ciphers.removeAll(_excludeCipherSuites);
+        return selected_ciphers.toArray(new String[selected_ciphers.size()]);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Check if the lifecycle has been started and throw runtime exception
+     */
+    protected void checkNotStarted()
+    {
+        if (isStarted())
+            throw new IllegalStateException("Cannot modify configuration when "+getState());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if CRL Distribution Points support is enabled
+     */
+    public boolean isEnableCRLDP()
+    {
+        return _enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables CRL Distribution Points Support
+     * @param enableCRLDP true - turn on, false - turns off
+     */
+    public void setEnableCRLDP(boolean enableCRLDP)
+    {
+        checkNotStarted();
+
+        _enableCRLDP = enableCRLDP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return true if On-Line Certificate Status Protocol support is enabled
+     */
+    public boolean isEnableOCSP()
+    {
+        return _enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Enables On-Line Certificate Status Protocol support
+     * @param enableOCSP true - turn on, false - turn off
+     */
+    public void setEnableOCSP(boolean enableOCSP)
+    {
+        checkNotStarted();
+
+        _enableOCSP = enableOCSP;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Location of the OCSP Responder
+     */
+    public String getOcspResponderURL()
+    {
+        return _ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the location of the OCSP Responder.
+     * @param ocspResponderURL location of the OCSP Responder
+     */
+    public void setOcspResponderURL(String ocspResponderURL)
+    {
+        checkNotStarted();
+
+        _ocspResponderURL = ocspResponderURL;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the key store.
+     * @param keyStore the key store to set
+     */
+    public void setKeyStore(KeyStore keyStore)
+    {
+        checkNotStarted();
+
+        _keyStore = keyStore;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the trust store.
+     * @param trustStore the trust store to set
+     */
+    public void setTrustStore(KeyStore trustStore)
+    {
+        checkNotStarted();
+
+        _trustStore = trustStore;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the key store resource.
+     * @param resource the key store resource to set
+     */
+    public void setKeyStoreResource(Resource resource)
+    {
+        checkNotStarted();
+
+        try
+        {
+            _keyStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the trust store resource.
+     * @param resource the trust store resource to set
+     */
+    public void setTrustStoreResource(Resource resource)
+    {
+        checkNotStarted();
+
+        try
+        {
+            _trustStoreInputStream = resource.getInputStream();
+        }
+        catch (IOException e)
+        {
+             throw new InvalidParameterException("Unable to get resource "+
+                     "input stream for resource "+resource.toString());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+    * @return true if SSL Session caching is enabled
+    */
+    public boolean isSessionCachingEnabled()
+    {
+        return _sessionCachingEnabled;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the flag to enable SSL Session caching.
+    * @param enableSessionCaching the value of the flag
+    */
+    public void setSessionCachingEnabled(boolean enableSessionCaching)
+    {
+        _sessionCachingEnabled = enableSessionCaching;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get SSL session cache size.
+     * @return SSL session cache size
+     */
+    public int getSslSessionCacheSize()
+    {
+        return _sslSessionCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** SEt SSL session cache size.
+     * @param sslSessionCacheSize SSL session cache size to set
+     */
+    public void setSslSessionCacheSize(int sslSessionCacheSize)
+    {
+        _sslSessionCacheSize = sslSessionCacheSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get SSL session timeout.
+     * @return SSL session timeout
+     */
+    public int getSslSessionTimeout()
+    {
+        return _sslSessionTimeout;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set SSL session timeout.
+     * @param sslSessionTimeout SSL session timeout to set
+     */
+    public void setSslSessionTimeout(int sslSessionTimeout)
+    {
+        _sslSessionTimeout = sslSessionTimeout;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public SSLServerSocket newSslServerSocket(String host,int port,int backlog) throws IOException
+    {
+        SSLServerSocketFactory factory = _context.getServerSocketFactory();
+
+        SSLServerSocket socket =
+            (SSLServerSocket) (host==null ?
+                        factory.createServerSocket(port,backlog):
+                        factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    /* ------------------------------------------------------------ */
+    public SSLSocket newSslSocket() throws IOException
+    {
+        SSLSocketFactory factory = _context.getSocketFactory();
+
+        SSLSocket socket = (SSLSocket)factory.createSocket();
+
+        if (getWantClientAuth())
+            socket.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            socket.setNeedClientAuth(getNeedClientAuth());
+
+        socket.setEnabledCipherSuites(selectCipherSuites(
+                                            socket.getEnabledCipherSuites(),
+                                            socket.getSupportedCipherSuites()));
+        socket.setEnabledProtocols(selectProtocols(socket.getEnabledProtocols(),socket.getSupportedProtocols()));
+
+        return socket;
+    }
+
+    /* ------------------------------------------------------------ */
+    public SSLEngine newSslEngine(String host,int port)
+    {
+        SSLEngine sslEngine=isSessionCachingEnabled()
+            ?_context.createSSLEngine(host, port)
+            :_context.createSSLEngine();
+
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /* ------------------------------------------------------------ */
+    public SSLEngine newSslEngine()
+    {
+        SSLEngine sslEngine=_context.createSSLEngine();
+        customize(sslEngine);
+        return sslEngine;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void customize(SSLEngine sslEngine)
+    {
+        if (getWantClientAuth())
+            sslEngine.setWantClientAuth(getWantClientAuth());
+        if (getNeedClientAuth())
+            sslEngine.setNeedClientAuth(getNeedClientAuth());
+
+        sslEngine.setEnabledCipherSuites(selectCipherSuites(
+                sslEngine.getEnabledCipherSuites(),
+                sslEngine.getSupportedCipherSuites()));
+
+        sslEngine.setEnabledProtocols(selectProtocols(sslEngine.getEnabledProtocols(),sslEngine.getSupportedProtocols()));
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return String.format("%s@%x(%s,%s)",
+                getClass().getSimpleName(),
+                hashCode(),
+                _keyStorePath,
+                _trustStorePath);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/statistic/CounterStatistic.java b/src/java/org/eclipse/jetty/util/statistic/CounterStatistic.java
new file mode 100644
index 0000000..24b0b93
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/statistic/CounterStatistic.java
@@ -0,0 +1,117 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/** Statistics on a counter value.
+ * <p>
+ * Keep total, current and maximum values of a counter that
+ * can be incremented and decremented. The total refers only
+ * to increments.
+ *
+ */
+public class CounterStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _curr = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+
+    /* ------------------------------------------------------------ */
+    public void reset()
+    {
+        reset(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void reset(final long value)
+    {
+        _max.set(value);
+        _curr.set(value);
+        _total.set(0); // total always set to 0 to properly calculate cumulative total
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param delta the amount to add to the count
+     */
+    public void add(final long delta)
+    {
+        long value=_curr.addAndGet(delta);
+        if (delta > 0)
+            _total.addAndGet(delta);
+        Atomics.updateMax(_max,value);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param delta the amount to subtract the count by.
+     */
+    public void subtract(final long delta)
+    {
+        add(-delta);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void increment()
+    {
+        add(1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void decrement()
+    {
+        add(-1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return current value
+     */
+    public long getCurrent()
+    {
+        return _curr.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return total value
+     */
+    public long getTotal()
+    {
+        return _total.get();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/statistic/SampleStatistic.java b/src/java/org/eclipse/jetty/util/statistic/SampleStatistic.java
new file mode 100644
index 0000000..abc1ed3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/statistic/SampleStatistic.java
@@ -0,0 +1,109 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.statistic;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.Atomics;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * SampledStatistics
+ * <p>
+ * Provides max, total, mean, count, variance, and standard
+ * deviation of continuous sequence of samples.
+ * <p>
+ * Calculates estimates of mean, variance, and standard deviation
+ * characteristics of a sample using a non synchronized
+ * approximation of the on-line algorithm presented
+ * in Donald Knuth's Art of Computer Programming, Volume 2,
+ * Seminumerical Algorithms, 3rd edition, page 232,
+ * Boston: Addison-Wesley. that cites a 1962 paper by B.P. Welford
+ * that can be found by following the link http://www.jstor.org/pss/1266577
+ * <p>
+ * This algorithm is also described in Wikipedia at
+ * http://en.wikipedia.org/w/index.php?title=Algorithms_for_calculating_variance&section=4#On-line_algorithm
+ */
+public class SampleStatistic
+{
+    protected final AtomicLong _max = new AtomicLong();
+    protected final AtomicLong _total = new AtomicLong();
+    protected final AtomicLong _count = new AtomicLong();
+    protected final AtomicLong _totalVariance100 = new AtomicLong();
+
+    public void reset()
+    {
+        _max.set(0);
+        _total.set(0);
+        _count.set(0);
+        _totalVariance100.set(0);
+    }
+
+    public void set(final long sample)
+    {
+        long total = _total.addAndGet(sample);
+        long count = _count.incrementAndGet();
+
+        if (count>1)
+        {
+            long mean10 = total*10/count;
+            long delta10 = sample*10 - mean10;
+            _totalVariance100.addAndGet(delta10*delta10);
+        }
+
+        Atomics.updateMax(_max, sample);
+    }
+
+    /**
+     * @return the max value
+     */
+    public long getMax()
+    {
+        return _max.get();
+    }
+
+    public long getTotal()
+    {
+        return _total.get();
+    }
+
+    public long getCount()
+    {
+        return _count.get();
+    }
+
+    public double getMean()
+    {
+        return (double)_total.get()/_count.get();
+    }
+
+    public double getVariance()
+    {
+        final long variance100 = _totalVariance100.get();
+        final long count = _count.get();
+
+        return count>1?((double)variance100)/100.0/(count-1):0.0;
+    }
+
+    public double getStdDev()
+    {
+        return Math.sqrt(getVariance());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java b/src/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
new file mode 100644
index 0000000..6ffc8d1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java
@@ -0,0 +1,184 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Jetty ThreadPool using java 5 ThreadPoolExecutor
+ * This class wraps a {@link ExecutorService} as a {@link ThreadPool} and
+ * {@link LifeCycle} interfaces so that it may be used by the Jetty <code>org.eclipse.jetty.server.Server</code>
+ */
+public class ExecutorThreadPool extends AbstractLifeCycle implements ThreadPool, LifeCycle
+{
+    private static final Logger LOG = Log.getLogger(ExecutorThreadPool.class);
+    private final ExecutorService _executor;
+
+    /* ------------------------------------------------------------ */
+    public ExecutorThreadPool(ExecutorService executor)
+    {
+        _executor = executor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds and
+     * an unbounded {@link LinkedBlockingQueue} is used for the job queue;
+     */
+    public ExecutorThreadPool()
+    {
+        // Using an unbounded queue makes the maxThreads parameter useless
+        // Refer to ThreadPoolExecutor javadocs for details
+        this(new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor}.
+     * Max pool size is 256, pool thread timeout after 60 seconds, and core pool size is 32 when queueSize >= 0.
+     * @param queueSize can be -1 for using an unbounded {@link LinkedBlockingQueue}, 0 for using a
+     * {@link SynchronousQueue}, greater than 0 for using a {@link ArrayBlockingQueue} of the given size.
+     */
+    public ExecutorThreadPool(int queueSize)
+    {
+        this(queueSize < 0 ? new ThreadPoolExecutor(256, 256, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()) :
+                queueSize == 0 ? new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) :
+                        new ThreadPoolExecutor(32, 256, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize)));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue;
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle, in milliseconds
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Wraps an {@link ThreadPoolExecutor} using
+     * an unbounded {@link LinkedBlockingQueue} is used for the jobs queue.
+     * @param corePoolSize must be equal to maximumPoolSize
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit)
+    {
+        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<Runnable>());
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Wraps an {@link ThreadPoolExecutor}
+     * @param corePoolSize the number of threads to keep in the pool, even if they are idle
+     * @param maximumPoolSize the maximum number of threads to allow in the pool
+     * @param keepAliveTime the max time a thread can remain idle
+     * @param unit the unit for the keepAliveTime
+     * @param workQueue the queue to use for holding tasks before they are executed
+     */
+    public ExecutorThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
+    {
+        this(new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue));
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean dispatch(Runnable job)
+    {
+        try
+        {
+            _executor.execute(job);
+            return true;
+        }
+        catch(RejectedExecutionException e)
+        {
+            LOG.warn(e);
+            return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getIdleThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public int getThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            return tpe.getPoolSize();
+        }
+        return -1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLowOnThreads()
+    {
+        if (_executor instanceof ThreadPoolExecutor)
+        {
+            final ThreadPoolExecutor tpe = (ThreadPoolExecutor)_executor;
+            // getActiveCount() locks the thread pool, so execute it last
+            return tpe.getPoolSize() == tpe.getMaximumPoolSize() &&
+                    tpe.getQueue().size() >= tpe.getPoolSize() - tpe.getActiveCount();
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void join() throws InterruptedException
+    {
+        _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        _executor.shutdownNow();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/src/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
new file mode 100644
index 0000000..da53c03
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -0,0 +1,678 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.eclipse.jetty.util.BlockingArrayQueue;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
+
+public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPool, Executor, Dumpable
+{
+    private static final Logger LOG = Log.getLogger(QueuedThreadPool.class);
+
+    private final AtomicInteger _threadsStarted = new AtomicInteger();
+    private final AtomicInteger _threadsIdle = new AtomicInteger();
+    private final AtomicLong _lastShrink = new AtomicLong();
+    private final ConcurrentLinkedQueue<Thread> _threads=new ConcurrentLinkedQueue<Thread>();
+    private final Object _joinLock = new Object();
+    private BlockingQueue<Runnable> _jobs;
+    private String _name;
+    private int _maxIdleTimeMs=60000;
+    private int _maxThreads=254;
+    private int _minThreads=8;
+    private int _maxQueued=-1;
+    private int _priority=Thread.NORM_PRIORITY;
+    private boolean _daemon=false;
+    private int _maxStopTime=100;
+    private boolean _detailedDump=false;
+
+    /* ------------------------------------------------------------------- */
+    /** Construct
+     */
+    public QueuedThreadPool()
+    {
+        _name="qtp"+super.hashCode();
+    }
+
+    /* ------------------------------------------------------------------- */
+    /** Construct
+     */
+    public QueuedThreadPool(int maxThreads)
+    {
+        this();
+        setMaxThreads(maxThreads);
+    }
+
+    /* ------------------------------------------------------------------- */
+    /** Construct
+     */
+    public QueuedThreadPool(BlockingQueue<Runnable> jobQ)
+    {
+        this();
+        _jobs=jobQ;
+        _jobs.clear();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStart() throws Exception
+    {
+        super.doStart();
+        _threadsStarted.set(0);
+
+        if (_jobs==null)
+        {
+            _jobs=_maxQueued>0 ?new ArrayBlockingQueue<Runnable>(_maxQueued)
+                :new BlockingArrayQueue<Runnable>(_minThreads,_minThreads);
+        }
+
+        int threads=_threadsStarted.get();
+        while (isRunning() && threads<_minThreads)
+        {
+            startThread(threads);
+            threads=_threadsStarted.get();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+        long start=System.currentTimeMillis();
+
+        // let jobs complete naturally for a while
+        while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < (_maxStopTime/2))
+            Thread.sleep(1);
+
+        // kill queued jobs and flush out idle jobs
+        _jobs.clear();
+        Runnable noop = new Runnable(){public void run(){}};
+        for  (int i=_threadsIdle.get();i-->0;)
+            _jobs.offer(noop);
+        Thread.yield();
+
+        // interrupt remaining threads
+        if (_threadsStarted.get()>0)
+            for (Thread thread : _threads)
+                thread.interrupt();
+
+        // wait for remaining threads to die
+        while (_threadsStarted.get()>0 && (System.currentTimeMillis()-start) < _maxStopTime)
+        {
+            Thread.sleep(1);
+        }
+        Thread.yield();
+        int size=_threads.size();
+        if (size>0)
+        {
+            LOG.warn(size+" threads could not be stopped");
+
+            if (size==1 || LOG.isDebugEnabled())
+            {
+                for (Thread unstopped : _threads)
+                {
+                    LOG.info("Couldn't stop "+unstopped);
+                    for (StackTraceElement element : unstopped.getStackTrace())
+                    {
+                        LOG.info(" at "+element);
+                    }
+                }
+            }
+        }
+
+        synchronized (_joinLock)
+        {
+            _joinLock.notifyAll();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    public void setDaemon(boolean daemon)
+    {
+        _daemon=daemon;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum thread idle time.
+     * Threads that are idle for longer than this period may be
+     * stopped.
+     * Delegated to the named or anonymous Pool.
+     * @see #getMaxIdleTimeMs
+     * @param maxIdleTimeMs Max idle time in ms.
+     */
+    public void setMaxIdleTimeMs(int maxIdleTimeMs)
+    {
+        _maxIdleTimeMs=maxIdleTimeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param stopTimeMs maximum total time that stop() will wait for threads to die.
+     */
+    public void setMaxStopTimeMs(int stopTimeMs)
+    {
+        _maxStopTime = stopTimeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     * @see #getMaxThreads
+     * @param maxThreads maximum number of threads.
+     */
+    public void setMaxThreads(int maxThreads)
+    {
+        _maxThreads=maxThreads;
+        if (_minThreads>_maxThreads)
+            _minThreads=_maxThreads;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     * @see #getMinThreads
+     * @param minThreads minimum number of threads
+     */
+    public void setMinThreads(int minThreads)
+    {
+        _minThreads=minThreads;
+
+        if (_minThreads>_maxThreads)
+            _maxThreads=_minThreads;
+
+        int threads=_threadsStarted.get();
+        while (isStarted() && threads<_minThreads)
+        {
+            startThread(threads);
+            threads=_threadsStarted.get();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name Name of the BoundedThreadPool to use when naming Threads.
+     */
+    public void setName(String name)
+    {
+        if (isRunning())
+            throw new IllegalStateException("started");
+        _name= name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the priority of the pool threads.
+     *  @param priority the new thread priority.
+     */
+    public void setThreadsPriority(int priority)
+    {
+        _priority=priority;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum queue size
+     */
+    public int getMaxQueued()
+    {
+        return _maxQueued;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param max job queue size
+     */
+    public void setMaxQueued(int max)
+    {
+        if (isRunning())
+            throw new IllegalStateException("started");
+        _maxQueued=max;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the maximum thread idle time.
+     * Delegated to the named or anonymous Pool.
+     * @see #setMaxIdleTimeMs
+     * @return Max idle time in ms.
+     */
+    public int getMaxIdleTimeMs()
+    {
+        return _maxIdleTimeMs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return maximum total time that stop() will wait for threads to die.
+     */
+    public int getMaxStopTimeMs()
+    {
+        return _maxStopTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the maximum number of threads.
+     * Delegated to the named or anonymous Pool.
+     * @see #setMaxThreads
+     * @return maximum number of threads.
+     */
+    public int getMaxThreads()
+    {
+        return _maxThreads;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the minimum number of threads.
+     * Delegated to the named or anonymous Pool.
+     * @see #setMinThreads
+     * @return minimum number of threads.
+     */
+    public int getMinThreads()
+    {
+        return _minThreads;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The name of the BoundedThreadPool.
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get the priority of the pool threads.
+     *  @return the priority of the pool threads.
+     */
+    public int getThreadsPriority()
+    {
+        return _priority;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Delegated to the named or anonymous Pool.
+     */
+    public boolean isDaemon()
+    {
+        return _daemon;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isDetailedDump()
+    {
+        return _detailedDump;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setDetailedDump(boolean detailedDump)
+    {
+        _detailedDump = detailedDump;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean dispatch(Runnable job)
+    {
+        if (isRunning())
+        {
+            final int jobQ = _jobs.size();
+            final int idle = getIdleThreads();
+            if(_jobs.offer(job))
+            {
+                // If we had no idle threads or the jobQ is greater than the idle threads
+                if (idle==0 || jobQ>idle)
+                {
+                    int threads=_threadsStarted.get();
+                    if (threads<_maxThreads)
+                        startThread(threads);
+                }
+                return true;
+            }
+        }
+        LOG.debug("Dispatched {} to stopped {}",job,this);
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void execute(Runnable job)
+    {
+        if (!dispatch(job))
+            throw new RejectedExecutionException();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    public void join() throws InterruptedException
+    {
+        synchronized (_joinLock)
+        {
+            while (isRunning())
+                _joinLock.wait();
+        }
+
+        while (isStopping())
+            Thread.sleep(1);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    public int getThreads()
+    {
+        return _threadsStarted.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The number of idle threads in the pool
+     */
+    public int getIdleThreads()
+    {
+        return _threadsIdle.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the pool is at maxThreads and there are not more idle threads than queued jobs
+     */
+    public boolean isLowOnThreads()
+    {
+        return _threadsStarted.get()==_maxThreads && _jobs.size()>=_threadsIdle.get();
+    }
+
+    /* ------------------------------------------------------------ */
+    private boolean startThread(int threads)
+    {
+        final int next=threads+1;
+        if (!_threadsStarted.compareAndSet(threads,next))
+            return false;
+
+        boolean started=false;
+        try
+        {
+            Thread thread=newThread(_runnable);
+            thread.setDaemon(_daemon);
+            thread.setPriority(_priority);
+            thread.setName(_name+"-"+thread.getId());
+            _threads.add(thread);
+
+            thread.start();
+            started=true;
+        }
+        finally
+        {
+            if (!started)
+                _threadsStarted.decrementAndGet();
+        }
+        return started;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected Thread newThread(Runnable runnable)
+    {
+        return new Thread(runnable);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String dump()
+    {
+        return AggregateLifeCycle.dump(this);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void dump(Appendable out, String indent) throws IOException
+    {
+        List<Object> dump = new ArrayList<Object>(getMaxThreads());
+        for (final Thread thread: _threads)
+        {
+            final StackTraceElement[] trace=thread.getStackTrace();
+            boolean inIdleJobPoll=false;
+            // trace can be null on early java 6 jvms
+            if (trace != null)
+            {
+                for (StackTraceElement t : trace)
+                {
+                    if ("idleJobPoll".equals(t.getMethodName()))
+                    {
+                        inIdleJobPoll = true;
+                        break;
+                    }
+                }
+            }
+            final boolean idle=inIdleJobPoll;
+
+            if (_detailedDump)
+            {
+                dump.add(new Dumpable()
+                {
+                    public void dump(Appendable out, String indent) throws IOException
+                    {
+                        out.append(String.valueOf(thread.getId())).append(' ').append(thread.getName()).append(' ').append(thread.getState().toString()).append(idle?" IDLE":"").append('\n');
+                        if (!idle)
+                            AggregateLifeCycle.dump(out,indent,Arrays.asList(trace));
+                    }
+
+                    public String dump()
+                    {
+                        return null;
+                    }
+                });
+            }
+            else
+            {
+                dump.add(thread.getId()+" "+thread.getName()+" "+thread.getState()+" @ "+(trace.length>0?trace[0]:"???")+(idle?" IDLE":""));
+            }
+        }
+
+        AggregateLifeCycle.dumpObject(out,this);
+        AggregateLifeCycle.dump(out,indent,dump);
+
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return _name+"{"+getMinThreads()+"<="+getIdleThreads()+"<="+getThreads()+"/"+getMaxThreads()+","+(_jobs==null?-1:_jobs.size())+"}";
+    }
+
+    /* ------------------------------------------------------------ */
+    private Runnable idleJobPoll() throws InterruptedException
+    {
+        return _jobs.poll(_maxIdleTimeMs,TimeUnit.MILLISECONDS);
+    }
+
+    /* ------------------------------------------------------------ */
+    private Runnable _runnable = new Runnable()
+    {
+        public void run()
+        {
+            boolean shrink=false;
+            try
+            {
+                Runnable job=_jobs.poll();
+                while (isRunning())
+                {
+                    // Job loop
+                    while (job!=null && isRunning())
+                    {
+                        runJob(job);
+                        job=_jobs.poll();
+                    }
+
+                    // Idle loop
+                    try
+                    {
+                        _threadsIdle.incrementAndGet();
+
+                        while (isRunning() && job==null)
+                        {
+                            if (_maxIdleTimeMs<=0)
+                                job=_jobs.take();
+                            else
+                            {
+                                // maybe we should shrink?
+                                final int size=_threadsStarted.get();
+                                if (size>_minThreads)
+                                {
+                                    long last=_lastShrink.get();
+                                    long now=System.currentTimeMillis();
+                                    if (last==0 || (now-last)>_maxIdleTimeMs)
+                                    {
+                                        shrink=_lastShrink.compareAndSet(last,now) &&
+                                        _threadsStarted.compareAndSet(size,size-1);
+                                        if (shrink)
+                                            return;
+                                    }
+                                }
+                                job=idleJobPoll();
+                            }
+                        }
+                    }
+                    finally
+                    {
+                        _threadsIdle.decrementAndGet();
+                    }
+                }
+            }
+            catch(InterruptedException e)
+            {
+                LOG.ignore(e);
+            }
+            catch(Exception e)
+            {
+                LOG.warn(e);
+            }
+            finally
+            {
+                if (!shrink)
+                    _threadsStarted.decrementAndGet();
+                _threads.remove(Thread.currentThread());
+            }
+        }
+    };
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Runs the given job in the {@link Thread#currentThread() current thread}.</p>
+     * <p>Subclasses may override to perform pre/post actions before/after the job is run.</p>
+     *
+     * @param job the job to run
+     */
+    protected void runJob(Runnable job)
+    {
+        job.run();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the job queue
+     */
+    protected BlockingQueue<Runnable> getQueue()
+    {
+        return _jobs;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param id The thread ID to stop.
+     * @return true if the thread was found and stopped.
+     * @deprecated Use {@link #interruptThread(long)} in preference
+     */
+    @Deprecated
+    public boolean stopThread(long id)
+    {
+        for (Thread thread: _threads)
+        {
+            if (thread.getId()==id)
+            {
+                thread.stop();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    public boolean interruptThread(long id)
+    {
+        for (Thread thread: _threads)
+        {
+            if (thread.getId()==id)
+            {
+                thread.interrupt();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param id The thread ID to interrupt.
+     * @return true if the thread was found and interrupted.
+     */
+    public String dumpThread(long id)
+    {
+        for (Thread thread: _threads)
+        {
+            if (thread.getId()==id)
+            {
+                StringBuilder buf = new StringBuilder();
+                buf.append(thread.getId()).append(" ").append(thread.getName()).append(" ").append(thread.getState()).append(":\n");
+                for (StackTraceElement element : thread.getStackTrace())
+                    buf.append("  at ").append(element.toString()).append('\n');
+                return buf.toString();
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/thread/ShutdownThread.java b/src/java/org/eclipse/jetty/util/thread/ShutdownThread.java
new file mode 100644
index 0000000..168b444
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/thread/ShutdownThread.java
@@ -0,0 +1,148 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.eclipse.jetty.util.component.Destroyable;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ShutdownThread is a shutdown hook thread implemented as 
+ * singleton that maintains a list of lifecycle instances
+ * that are registered with it and provides ability to stop
+ * these lifecycles upon shutdown of the Java Virtual Machine 
+ */
+public class ShutdownThread extends Thread
+{
+    private static final Logger LOG = Log.getLogger(ShutdownThread.class);
+    private static final ShutdownThread _thread = new ShutdownThread();
+
+    private boolean _hooked;
+    private final List<LifeCycle> _lifeCycles = new CopyOnWriteArrayList<LifeCycle>();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Default constructor for the singleton
+     * 
+     * Registers the instance as shutdown hook with the Java Runtime
+     */
+    private ShutdownThread()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void hook()
+    {
+        try
+        {
+            if (!_hooked)
+                Runtime.getRuntime().addShutdownHook(this);
+            _hooked=true;
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.info("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    private synchronized void unhook()
+    {
+        try
+        {
+            _hooked=false;
+            Runtime.getRuntime().removeShutdownHook(this);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+            LOG.debug("shutdown already commenced");
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Returns the instance of the singleton
+     * 
+     * @return the singleton instance of the {@link ShutdownThread}
+     */
+    public static ShutdownThread getInstance()
+    {
+        return _thread;
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+
+    /* ------------------------------------------------------------ */
+    public static synchronized void register(int index, LifeCycle... lifeCycles)
+    {
+        _thread._lifeCycles.addAll(index,Arrays.asList(lifeCycles));
+        if (_thread._lifeCycles.size()>0)
+            _thread.hook();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public static synchronized void deregister(LifeCycle lifeCycle)
+    {
+        _thread._lifeCycles.remove(lifeCycle);
+        if (_thread._lifeCycles.size()==0)
+            _thread.unhook();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void run()
+    {
+        for (LifeCycle lifeCycle : _thread._lifeCycles)
+        {
+            try
+            {
+                if (lifeCycle.isStarted())
+                {
+                    lifeCycle.stop();
+                    LOG.debug("Stopped {}",lifeCycle);
+                }
+                
+                if (lifeCycle instanceof Destroyable)
+                {
+                    ((Destroyable)lifeCycle).destroy();
+                    LOG.debug("Destroyed {}",lifeCycle);
+                }
+            }
+            catch (Exception ex)
+            {
+                LOG.debug(ex);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/thread/ThreadPool.java b/src/java/org/eclipse/jetty/util/thread/ThreadPool.java
new file mode 100644
index 0000000..99562cc
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/thread/ThreadPool.java
@@ -0,0 +1,67 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import org.eclipse.jetty.util.component.LifeCycle;
+
+/* ------------------------------------------------------------ */
+/** ThreadPool.
+ * 
+ *
+ */
+public interface ThreadPool
+{
+    /* ------------------------------------------------------------ */
+    public abstract boolean dispatch(Runnable job);
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Blocks until the thread pool is {@link LifeCycle#stop stopped}.
+     */
+    public void join() throws InterruptedException;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The total number of threads currently in the pool
+     */
+    public int getThreads();
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The number of idle threads in the pool
+     */
+    public int getIdleThreads();
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the pool is low on threads
+     */
+    public boolean isLowOnThreads();
+    
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface SizedThreadPool extends ThreadPool
+    {
+        public int getMinThreads();
+        public int getMaxThreads();
+        public void setMinThreads(int threads);
+        public void setMaxThreads(int threads);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/util/thread/Timeout.java b/src/java/org/eclipse/jetty/util/thread/Timeout.java
new file mode 100644
index 0000000..6fabbdd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/util/thread/Timeout.java
@@ -0,0 +1,380 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** Timeout queue.
+ * This class implements a timeout queue for timers that are at least as likely to be cancelled as they are to expire.
+ * Unlike the util timeout class, the duration of the timeouts is shared by all scheduled tasks and if the duration 
+ * is changed, this affects all scheduled tasks.
+ * <p>
+ * The nested class Task should be extended by users of this class to obtain call back notification of 
+ * expires. 
+ */
+public class Timeout
+{
+    private static final Logger LOG = Log.getLogger(Timeout.class);
+    private Object _lock;
+    private long _duration;
+    private volatile long _now=System.currentTimeMillis();
+    private Task _head=new Task();
+
+    /* ------------------------------------------------------------ */
+    public Timeout()
+    {
+        _lock=new Object();
+        _head._timeout=this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Timeout(Object lock)
+    {
+        _lock=lock;
+        _head._timeout=this;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the duration.
+     */
+    public long getDuration()
+    {
+        return _duration;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param duration The duration to set.
+     */
+    public void setDuration(long duration)
+    {
+        _duration = duration;
+    }
+
+    /* ------------------------------------------------------------ */
+    public long setNow()
+    {
+        return _now=System.currentTimeMillis();
+    }
+    
+    /* ------------------------------------------------------------ */
+    public long getNow()
+    {
+        return _now;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setNow(long now)
+    {
+        _now=now;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get an expired tasks.
+     * This is called instead of {@link #tick()} to obtain the next
+     * expired Task, but without calling it's {@link Task#expire()} or
+     * {@link Task#expired()} methods.
+     * 
+     * @return the next expired task or null.
+     */
+    public Task expired()
+    {
+        synchronized (_lock)
+        {
+            long _expiry = _now-_duration;
+
+            if (_head._next!=_head)
+            {
+                Task task = _head._next;
+                if (task._timestamp>_expiry)
+                    return null;
+
+                task.unlink();
+                task._expired=true;
+                return task;
+            }
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void tick()
+    {
+        final long expiry = _now-_duration;
+
+        Task task=null;
+        while (true)
+        {
+            try
+            {
+                synchronized (_lock)
+                {
+                    task= _head._next;
+                    if (task==_head || task._timestamp>expiry)
+                        break;
+                    task.unlink();
+                    task._expired=true;
+                    task.expire();
+                }
+                
+                task.expired();
+            }
+            catch(Throwable th)
+            {
+                LOG.warn(Log.EXCEPTION,th);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void tick(long now)
+    {
+        _now=now;
+        tick();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void schedule(Task task)
+    {
+        schedule(task,0L);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param task
+     * @param delay A delay in addition to the default duration of the timeout
+     */
+    public void schedule(Task task,long delay)
+    {
+        synchronized (_lock)
+        {
+            if (task._timestamp!=0)
+            {
+                task.unlink();
+                task._timestamp=0;
+            }
+            task._timeout=this;
+            task._expired=false;
+            task._delay=delay;
+            task._timestamp = _now+delay;
+
+            Task last=_head._prev;
+            while (last!=_head)
+            {
+                if (last._timestamp <= task._timestamp)
+                    break;
+                last=last._prev;
+            }
+            last.link(task);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void cancelAll()
+    {
+        synchronized (_lock)
+        {
+            _head._next=_head._prev=_head;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isEmpty()
+    {
+        synchronized (_lock)
+        {
+            return _head._next==_head;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public long getTimeToNext()
+    {
+        synchronized (_lock)
+        {
+            if (_head._next==_head)
+                return -1;
+            long to_next = _duration+_head._next._timestamp-_now;
+            return to_next<0?0:to_next;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        StringBuffer buf = new StringBuffer();
+        buf.append(super.toString());
+        
+        Task task = _head._next;
+        while (task!=_head)
+        {
+            buf.append("-->");
+            buf.append(task);
+            task=task._next;
+        }
+        
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /** Task.
+     * The base class for scheduled timeouts.  This class should be
+     * extended to implement the expire() method, which is called if the
+     * timeout expires.
+     * 
+     * 
+     *
+     */
+    public static class Task
+    {
+        Task _next;
+        Task _prev;
+        Timeout _timeout;
+        long _delay;
+        long _timestamp=0;
+        boolean _expired=false;
+
+        /* ------------------------------------------------------------ */
+        protected Task()
+        {
+            _next=_prev=this;
+        }
+
+        /* ------------------------------------------------------------ */
+        public long getTimestamp()
+        {
+            return _timestamp;
+        }
+
+        /* ------------------------------------------------------------ */
+        public long getAge()
+        {
+            final Timeout t = _timeout;
+            if (t!=null)
+            {
+                final long now=t._now;
+                if (now!=0 && _timestamp!=0)
+                    return now-_timestamp;
+            }
+            return 0;
+        }
+
+        /* ------------------------------------------------------------ */
+        private void unlink()
+        {
+            _next._prev=_prev;
+            _prev._next=_next;
+            _next=_prev=this;
+            _expired=false;
+        }
+
+        /* ------------------------------------------------------------ */
+        private void link(Task task)
+        {
+            Task next_next = _next;
+            _next._prev=task;
+            _next=task;
+            _next._next=next_next;
+            _next._prev=this;   
+        }
+        
+        /* ------------------------------------------------------------ */
+        /** Schedule the task on the given timeout.
+         * The task exiry will be called after the timeout duration.
+         * @param timer
+         */
+        public void schedule(Timeout timer)
+        {
+            timer.schedule(this);
+        }
+        
+        /* ------------------------------------------------------------ */
+        /** Schedule the task on the given timeout.
+         * The task exiry will be called after the timeout duration.
+         * @param timer
+         */
+        public void schedule(Timeout timer, long delay)
+        {
+            timer.schedule(this,delay);
+        }
+        
+        /* ------------------------------------------------------------ */
+        /** Reschedule the task on the current timeout.
+         * The task timeout is rescheduled as if it had been cancelled and
+         * scheduled on the current timeout.
+         */
+        public void reschedule()
+        {
+            Timeout timeout = _timeout;
+            if (timeout!=null)
+                timeout.schedule(this,_delay);
+        }
+        
+        /* ------------------------------------------------------------ */
+        /** Cancel the task.
+         * Remove the task from the timeout.
+         */
+        public void cancel()
+        {
+            Timeout timeout = _timeout;
+            if (timeout!=null)
+            {
+                synchronized (timeout._lock)
+                {
+                    unlink();
+                    _timestamp=0;
+                }
+            }
+        }
+        
+        /* ------------------------------------------------------------ */
+        public boolean isExpired() { return _expired; }
+
+        /* ------------------------------------------------------------ */
+	public boolean isScheduled() { return _next!=this; }
+        
+        /* ------------------------------------------------------------ */
+        /** Expire task.
+         * This method is called when the timeout expires. It is called
+         * in the scope of the synchronize block (on this) that sets 
+         * the {@link #isExpired()} state to true.
+         * @see #expired() For an unsynchronized callback.
+         */
+        protected void expire(){}
+
+        /* ------------------------------------------------------------ */
+        /** Expire task.
+         * This method is called when the timeout expires. It is called 
+         * outside of any synchronization scope and may be delayed. 
+         * 
+         */
+        public void expired(){}
+
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/webapp/AbstractConfiguration.java b/src/java/org/eclipse/jetty/webapp/AbstractConfiguration.java
new file mode 100644
index 0000000..3815aa2
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/AbstractConfiguration.java
@@ -0,0 +1,46 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+public class AbstractConfiguration implements Configuration
+{
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+    }
+
+    public void configure(WebAppContext context) throws Exception
+    {
+    }
+
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+    }
+
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+    }
+
+    public void destroy(WebAppContext context) throws Exception
+    {
+    }
+
+    public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
+    {
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/ClasspathPattern.java b/src/java/org/eclipse/jetty/webapp/ClasspathPattern.java
new file mode 100644
index 0000000..c66a540
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/ClasspathPattern.java
@@ -0,0 +1,230 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.webapp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * ClasspathPattern performs sequential pattern matching of a class name 
+ * against an internal array of classpath pattern entries.
+ * 
+ * When an entry starts with '-' (minus), reverse matching is performed.
+ * When an entry ends with '.' (period), prefix matching is performed.
+ * 
+ * When class is initialized from a classpath pattern string, entries 
+ * in this string should be separated by ':' (semicolon) or ',' (comma).
+ */
+
+public class ClasspathPattern
+{
+    private static class Entry
+    {
+        public String classpath = null;
+        public boolean result = false;
+        public boolean partial = false;      
+    }
+    
+    final private List<String> _patterns = new ArrayList<String>();
+    final private List<Entry> _entries = new ArrayList<Entry>();
+    
+    /* ------------------------------------------------------------ */
+    public ClasspathPattern()
+    {
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ClasspathPattern(String[] patterns)
+    {
+        setPatterns(patterns);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public ClasspathPattern(String pattern)
+    {
+        setPattern(pattern);
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize the matcher by parsing each classpath pattern in an array
+     * 
+     * @param patterns array of classpath patterns
+     */
+    private void setPatterns(String[] patterns)
+    {
+        _patterns.clear();
+        _entries.clear();
+        addPatterns(patterns);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize the matcher by parsing each classpath pattern in an array
+     * 
+     * @param patterns array of classpath patterns
+     */
+    private void addPatterns(String[] patterns)
+    {
+        if (patterns != null)
+        {
+            Entry entry = null; 
+            for (String pattern : patterns)
+            {
+                entry = createEntry(pattern);
+                if (entry != null) {
+                    _patterns.add(pattern);
+                    _entries.add(entry);
+                }
+            }
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Create an entry object containing information about 
+     * a single classpath pattern
+     * 
+     * @param pattern single classpath pattern
+     * @return corresponding Entry object
+     */
+    private Entry createEntry(String pattern)
+    {
+        Entry entry = null;
+        
+        if (pattern != null)
+        {
+            String item = pattern.trim();
+            if (item.length() > 0)
+            {
+                entry = new Entry();
+                entry.result = !item.startsWith("-");
+                entry.partial = item.endsWith(".");
+                entry.classpath = entry.result ? item : item.substring(1).trim();
+            }
+        }
+        return entry;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Initialize the matcher by parsing a classpath pattern string
+     * 
+     * @param pattern classpath pattern string
+     */
+    public void setPattern(String pattern)
+    {
+        _patterns.clear();
+        _entries.clear();
+        addPattern(pattern);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Parse a classpath pattern string and appending the result
+     * to the existing configuration.
+     * 
+     * @param pattern classpath pattern string
+     */
+    public void addPattern(String pattern)
+    {
+        ArrayList<String> patterns = new ArrayList<String>();
+        StringTokenizer entries = new StringTokenizer(pattern, ":,");
+        while (entries.hasMoreTokens())
+        {
+            patterns.add(entries.nextToken());
+        }
+        
+        addPatterns((String[])patterns.toArray(new String[patterns.size()]));
+    }   
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return array of classpath patterns
+     */
+    public String[] getPatterns()
+    {
+        String[] patterns = null;
+        
+        if (_patterns!=null && _patterns.size() > 0)
+        {
+            patterns = _patterns.toArray(new String[_patterns.size()]);
+        }
+        
+        return patterns;
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Match the class name against the pattern
+     *
+     * @param name name of the class to match
+     * @return true if class matches the pattern
+     */
+    public boolean match(String name)
+    {       
+        boolean result=false;
+
+        if (_entries != null)
+        {
+            name = name.replace('/','.');
+
+            int startIndex = 0;
+
+            while(startIndex < name.length() && name.charAt(startIndex) == '.') {
+                startIndex++;
+            }
+
+            int dollar = name.indexOf("$");
+
+            int endIndex =  dollar != -1 ? dollar : name.length();
+
+            for (Entry entry : _entries)
+            {
+                if (entry != null)
+                {               
+                    if (entry.partial)
+                    {
+                        if (name.regionMatches(startIndex, entry.classpath, 0, entry.classpath.length()))
+                        {
+                            result = entry.result;
+                            break;
+                        }
+                    }
+                    else
+                    {
+                        int regionLength = endIndex-startIndex;
+                        if (regionLength == entry.classpath.length()
+                                && name.regionMatches(startIndex, entry.classpath, 0, regionLength))
+                        {
+                            result = entry.result;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/CloneConfiguration.java b/src/java/org/eclipse/jetty/webapp/CloneConfiguration.java
new file mode 100644
index 0000000..159d17f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/CloneConfiguration.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+public class CloneConfiguration extends AbstractConfiguration
+{
+    final WebAppContext _template;
+    
+    CloneConfiguration(WebAppContext template)
+    {
+        _template=template;
+    }
+    
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    {
+        for (Configuration configuration : _template.getConfigurations())
+            configuration.cloneConfigure(_template,context);
+    }
+
+
+    @Override
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+        for (Configuration configuration : _template.getConfigurations())
+            configuration.deconfigure(context);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/Configuration.java b/src/java/org/eclipse/jetty/webapp/Configuration.java
new file mode 100644
index 0000000..85bee74
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/Configuration.java
@@ -0,0 +1,87 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+
+/* ------------------------------------------------------------------------------- */
+/** Base Class for WebApplicationContext Configuration.
+ * This class can be extended to customize or extend the configuration
+ * of the WebApplicationContext. 
+ */
+public interface Configuration 
+{
+
+    /* ------------------------------------------------------------------------------- */
+    /** Set up for configuration.
+     * <p>
+     * Typically this step discovers configuration resources
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void preConfigure (WebAppContext context) throws Exception;
+    
+    
+    /* ------------------------------------------------------------------------------- */
+    /** Configure WebApp.
+     * <p>
+     * Typically this step applies the discovered configuration resources to
+     * either the {@link WebAppContext} or the associated {@link MetaData}.
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void configure (WebAppContext context) throws Exception;
+    
+    
+    /* ------------------------------------------------------------------------------- */
+    /** Clear down after configuration.
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void postConfigure (WebAppContext context) throws Exception;
+    
+    /* ------------------------------------------------------------------------------- */
+    /** DeConfigure WebApp.
+     * This method is called to undo all configuration done. This is
+     * called to allow the context to work correctly over a stop/start cycle
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void deconfigure (WebAppContext context) throws Exception;
+
+    /* ------------------------------------------------------------------------------- */
+    /** Destroy WebApp.
+     * This method is called to destroy a webappcontext. It is typically called when a context 
+     * is removed from a server handler hierarchy by the deployer.
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void destroy (WebAppContext context) throws Exception;
+    
+
+    /* ------------------------------------------------------------------------------- */
+    /** Clone configuration instance.
+     * <p>
+     * Configure an instance of a WebAppContext, based on a template WebAppContext that 
+     * has previously been configured by this Configuration.
+     * @param template The template context
+     * @param context The context to configure
+     * @throws Exception
+     */
+    public void cloneConfigure (WebAppContext template, WebAppContext context) throws Exception;
+}
diff --git a/src/java/org/eclipse/jetty/webapp/DefaultsDescriptor.java b/src/java/org/eclipse/jetty/webapp/DefaultsDescriptor.java
new file mode 100644
index 0000000..64a0653
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/DefaultsDescriptor.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * DefaultsDescriptor
+ *
+ */
+public class DefaultsDescriptor extends WebDescriptor
+{
+    public DefaultsDescriptor(Resource xml)
+    {
+        super(xml);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/Descriptor.java b/src/java/org/eclipse/jetty/webapp/Descriptor.java
new file mode 100644
index 0000000..52ab6ce
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/Descriptor.java
@@ -0,0 +1,88 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.net.URL;
+
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlParser;
+
+public abstract class Descriptor
+{
+    protected Resource _xml;
+    protected XmlParser.Node _root;
+    protected XmlParser _parser;
+    protected boolean _validating;
+    
+    public Descriptor (Resource xml)
+    {
+        _xml = xml;
+    }
+    
+    public abstract XmlParser newParser()
+    throws ClassNotFoundException;
+    
+    public abstract void ensureParser()
+    throws ClassNotFoundException;
+    
+    protected void redirect(XmlParser parser, String resource, URL source)
+    {
+        if (source != null) parser.redirectEntity(resource, source);
+    }
+    
+    
+    public void setValidating (boolean validating)
+    {
+       _validating = validating;
+    }
+    
+    public void parse ()
+    throws Exception
+    {
+        if (_parser == null)
+           ensureParser();
+        
+        if (_root == null)
+        {
+            try
+            {
+                _root = _parser.parse(_xml.getInputStream());
+            }
+            finally
+            {
+                _xml.release();
+            }
+        }
+    }
+    
+    public Resource getResource ()
+    {
+        return _xml;
+    }
+    
+    public XmlParser.Node getRoot ()
+    {
+        return _root;
+    }
+    
+    public String toString()
+    {
+        return this.getClass().getSimpleName()+"("+_xml+")";
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/DescriptorProcessor.java b/src/java/org/eclipse/jetty/webapp/DescriptorProcessor.java
new file mode 100644
index 0000000..328f9f5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/DescriptorProcessor.java
@@ -0,0 +1,29 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+/**
+ * DescriptorProcessor
+ *
+ *
+ */
+public interface DescriptorProcessor
+{
+    public void process (WebAppContext context, Descriptor descriptor) throws Exception;
+}
diff --git a/src/java/org/eclipse/jetty/webapp/DiscoveredAnnotation.java b/src/java/org/eclipse/jetty/webapp/DiscoveredAnnotation.java
new file mode 100644
index 0000000..697e60e
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/DiscoveredAnnotation.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * DiscoveredAnnotation
+ *
+ * Represents an annotation that has been discovered
+ * by scanning source code of WEB-INF/classes and WEB-INF/lib jars.
+ * 
+ */
+public abstract class DiscoveredAnnotation
+{
+    private static final Logger LOG = Log.getLogger(DiscoveredAnnotation.class);
+
+    protected WebAppContext _context;
+    protected String _className;
+    protected Class<?> _clazz;
+    protected Resource _resource; //resource it was discovered on, can be null (eg from WEB-INF/classes)
+    
+    public abstract void apply();
+    
+    public DiscoveredAnnotation (WebAppContext context, String className)
+    {
+        this(context,className, null);
+    } 
+    
+    
+    public DiscoveredAnnotation(WebAppContext context, String className, Resource resource)
+    {
+        _context = context;
+        _className = className;
+        _resource = resource;
+    }
+    
+    public Resource getResource ()
+    {
+        return _resource;
+    }
+    
+    public Class<?> getTargetClass()
+    {
+        if (_clazz != null)
+            return _clazz;
+        
+        loadClass();
+        
+        return _clazz;
+    }
+    
+    private void loadClass ()
+    {
+        if (_clazz != null)
+            return;
+        
+        if (_className == null)
+            return;
+        
+        try
+        {
+            _clazz = Loader.loadClass(null, _className);
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+    }  
+}
diff --git a/src/java/org/eclipse/jetty/webapp/FragmentConfiguration.java b/src/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
new file mode 100644
index 0000000..05a97e1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/FragmentConfiguration.java
@@ -0,0 +1,89 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.webapp;
+
+import java.util.List;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * FragmentConfiguration
+ * 
+ * 
+ * 
+ * Process web-fragments in jars
+ */
+public class FragmentConfiguration extends AbstractConfiguration
+{
+    public final static String FRAGMENT_RESOURCES="org.eclipse.jetty.webFragments";
+    
+    @Override
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        if (!context.isConfigurationDiscovered())
+            return;
+
+        //find all web-fragment.xmls
+        findWebFragments(context, context.getMetaData());
+        
+    }
+
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    { 
+        if (!context.isConfigurationDiscovered())
+            return;
+        
+        //order the fragments
+        context.getMetaData().orderFragments(); 
+    }
+
+    @Override
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        context.setAttribute(FRAGMENT_RESOURCES, null);
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Look for any web-fragment.xml fragments in META-INF of jars in WEB-INF/lib
+     * 
+     * @throws Exception
+     */
+    public void findWebFragments (final WebAppContext context, final MetaData metaData) throws Exception
+    {
+        @SuppressWarnings("unchecked")
+        List<Resource> frags = (List<Resource>)context.getAttribute(FRAGMENT_RESOURCES);
+        if (frags!=null)
+        {
+            for (Resource frag : frags)
+            {
+            	if (frag.isDirectory()) //tolerate the case where the library is a directory, not a jar. useful for OSGi for example
+            	{
+                    metaData.addFragment(frag, Resource.newResource(frag.getURL()+"/META-INF/web-fragment.xml"));            		
+            	}
+                else //the standard case: a jar most likely inside WEB-INF/lib
+                {
+                    metaData.addFragment(frag, Resource.newResource("jar:"+frag.getURL()+"!/META-INF/web-fragment.xml"));
+                }
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/FragmentDescriptor.java b/src/java/org/eclipse/jetty/webapp/FragmentDescriptor.java
new file mode 100644
index 0000000..abeb1e4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/FragmentDescriptor.java
@@ -0,0 +1,169 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlParser;
+
+
+/**
+ * Fragment
+ *
+ * A web-fragment.xml descriptor.
+ */
+public class FragmentDescriptor extends WebDescriptor
+{
+    public static final String NAMELESS = "@@-NAMELESS-@@"; //prefix for nameless Fragments
+    protected static int _counter = 0;
+
+    public enum OtherType {None, Before, After};
+    protected OtherType _otherType = OtherType.None;
+   
+    
+    protected List<String> _befores = new ArrayList<String>();
+    protected List<String> _afters = new ArrayList<String>();
+    protected String _name;
+    
+
+    public FragmentDescriptor (Resource xml)
+        throws Exception
+    {
+        super (xml);
+    }       
+    
+    public String getName ()
+    {
+        return _name;
+    }
+    
+    @Override
+    public void parse () 
+        throws Exception
+    {
+        super.parse();
+        processName();
+    }
+    
+    public void processName ()
+    {
+        XmlParser.Node root = getRoot();
+        XmlParser.Node nameNode = root.get("name");
+        _name = NAMELESS+(_counter++);
+        if (nameNode != null)
+        {
+            String tmp = nameNode.toString(false,true);
+            if (tmp!=null && tmp.length()>0)
+                _name = tmp;
+        }
+    }
+    @Override
+    public void processOrdering ()
+    {
+        //Process a fragment jar's web-fragment.xml<ordering> elements
+        XmlParser.Node root = getRoot();       
+        
+        XmlParser.Node ordering = root.get("ordering");
+        if (ordering == null)
+            return; //No ordering for this fragment
+        
+        _isOrdered = true;
+   
+        processBefores(ordering);
+        processAfters(ordering);
+    }
+    
+    
+    public void processBefores (XmlParser.Node ordering)
+    {
+        //Process the <before> elements, looking for an <others/> clause and all of the <name> clauses
+        XmlParser.Node before = ordering.get("before");
+        if (before == null)
+            return;
+
+        Iterator<?> iter = before.iterator();
+        XmlParser.Node node = null;
+        while (iter.hasNext())
+        {
+            Object o = iter.next();
+            if (!(o instanceof XmlParser.Node)) continue;
+            node = (XmlParser.Node) o;
+            if (node.getTag().equalsIgnoreCase("others"))
+            {
+                if (_otherType != OtherType.None)
+                    throw new IllegalStateException("Duplicate <other> clause detected in "+_xml.getURI());
+
+                _otherType = OtherType.Before;
+            }
+            else if (node.getTag().equalsIgnoreCase("name"))
+                _befores.add(node.toString(false,true));
+        }
+    }
+
+    public void processAfters (XmlParser.Node ordering)
+    {
+        //Process the <after> elements, look for an <others/> clause and all of the <name/> clauses
+        XmlParser.Node after = ordering.get("after");
+        if (after == null)
+            return;
+        
+        Iterator<?> iter = after.iterator();
+        XmlParser.Node node = null;
+        while (iter.hasNext())
+        {
+            Object o = iter.next();
+            if (!(o instanceof XmlParser.Node)) continue;
+            node = (XmlParser.Node) o;
+            if (node.getTag().equalsIgnoreCase("others"))
+            {
+                if (_otherType != OtherType.None)
+                    throw new IllegalStateException("Duplicate <other> clause detected in "+_xml.getURI());
+
+                _otherType = OtherType.After;
+
+            }
+            else if (node.getTag().equalsIgnoreCase("name"))
+                _afters.add(node.toString(false,true));
+        }
+    }
+    
+    public List<String> getBefores()
+    {
+        return Collections.unmodifiableList(_befores);
+    }
+    
+    public List<String> getAfters()
+    {
+        return Collections.unmodifiableList(_afters);
+    }
+    
+    public OtherType getOtherType ()
+    {
+        return _otherType;
+    }
+    
+    public List<String> getOrdering()
+    {
+        return null; //only used for absolute-ordering in Descriptor
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/IterativeDescriptorProcessor.java b/src/java/org/eclipse/jetty/webapp/IterativeDescriptorProcessor.java
new file mode 100644
index 0000000..31ef913
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/IterativeDescriptorProcessor.java
@@ -0,0 +1,87 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.jetty.xml.XmlParser;
+
+/**
+ * IterativeDescriptorProcessor
+ *
+ *
+ */
+public abstract class IterativeDescriptorProcessor implements DescriptorProcessor
+{
+    public static final Class<?>[] __signature = new Class[]{WebAppContext.class, Descriptor.class, XmlParser.Node.class};
+    protected Map<String, Method> _visitors = new HashMap<String, Method>();
+    public abstract void start(WebAppContext context, Descriptor descriptor);
+    public abstract void end(WebAppContext context, Descriptor descriptor);
+
+    /**
+     * Register a method to be called back when visiting the node with the given name.
+     * The method must exist on a subclass of this class, and must have the signature:
+     * public void method (Descriptor descriptor, XmlParser.Node node)
+     * @param nodeName
+     * @param m
+     */
+    public void registerVisitor(String nodeName, Method m)
+    {
+        _visitors.put(nodeName, m);
+    }
+
+    
+    /** 
+     * {@inheritDoc}
+     */
+    public void process(WebAppContext context, Descriptor descriptor)
+    throws Exception
+    {
+        if (descriptor == null)
+            return;
+
+        start(context,descriptor);
+
+        XmlParser.Node root = descriptor.getRoot();
+        Iterator<?> iter = root.iterator();
+        XmlParser.Node node = null;
+        while (iter.hasNext())
+        {
+            Object o = iter.next();
+            if (!(o instanceof XmlParser.Node)) continue;
+            node = (XmlParser.Node) o;
+            visit(context, descriptor, node);
+        }
+
+        end(context,descriptor);
+    }
+
+
+    protected void visit (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    throws Exception
+    {
+        String name = node.getTag();
+        Method m =  _visitors.get(name);
+        if (m != null)
+            m.invoke(this, new Object[]{context, descriptor, node});
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/JarScanner.java b/src/java/org/eclipse/jetty/webapp/JarScanner.java
new file mode 100644
index 0000000..c5f6263
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/JarScanner.java
@@ -0,0 +1,171 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.webapp;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * JarScannerConfiguration
+ *
+ * Abstract base class for configurations that want to scan jars in
+ * WEB-INF/lib and the classloader hierarchy.
+ * 
+ * Jar name matching based on regexp patterns is provided.
+ * 
+ * Subclasses should implement the processEntry(URL jarUrl, JarEntry entry)
+ * method to handle entries in jar files whose names match the supplied 
+ * pattern.
+ */
+public abstract class JarScanner extends org.eclipse.jetty.util.PatternMatcher
+{
+    private static final Logger LOG = Log.getLogger(JarScanner.class);
+
+
+    public abstract void processEntry (URI jarUri, JarEntry entry);
+    
+    /**
+     * Find jar names from the provided list matching a pattern.
+     * 
+     * If the pattern is null and isNullInclusive is true, then
+     * all jar names will match.
+     * 
+     * A pattern is a set of acceptable jar names. Each acceptable
+     * jar name is a regex. Each regex can be separated by either a
+     * "," or a "|". If you use a "|" this or's together the jar
+     * name patterns. This means that ordering of the matches is
+     * unimportant to you. If instead, you want to match particular
+     * jar names, and you want to match them in order, you should
+     * separate the regexs with "," instead. 
+     * 
+     * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+     * Will iterate over the jar names and match
+     * in any order.
+     * 
+     * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+     * Will iterate over the jar names, matching
+     * all those starting with "aaa-" first, then "bbb-".
+     *
+     * @param pattern
+     * @param uris
+     * @param isNullInclusive if true, an empty pattern means all names match, if false, none match
+     * @throws Exception
+     */
+    public void scan (Pattern pattern, URI[] uris, boolean isNullInclusive)
+    throws Exception
+    {
+       super.match(pattern, uris, isNullInclusive);
+    }
+    
+    /**
+     * Find jar names from the classloader matching a pattern.
+     * 
+     * If the pattern is null and isNullInclusive is true, then
+     * all jar names in the classloader will match.
+     * 
+     * A pattern is a set of acceptable jar names. Each acceptable
+     * jar name is a regex. Each regex can be separated by either a
+     * "," or a "|". If you use a "|" this or's together the jar
+     * name patterns. This means that ordering of the matches is
+     * unimportant to you. If instead, you want to match particular
+     * jar names, and you want to match them in order, you should
+     * separate the regexs with "," instead. 
+     * 
+     * Eg "aaa-.*\\.jar|bbb-.*\\.jar"
+     * Will iterate over the jar names in the classloader and match
+     * in any order.
+     * 
+     * Eg "aaa-*\\.jar,bbb-.*\\.jar"
+     * Will iterate over the jar names in the classloader, matching
+     * all those starting with "aaa-" first, then "bbb-".
+     * 
+     * If visitParent is true, then the pattern is applied to the
+     * parent loader hierarchy. If false, it is only applied to the
+     * classloader passed in.
+     * 
+     * @param pattern
+     * @param loader
+     * @param isNullInclusive
+     * @param visitParent
+     * @throws Exception
+     */
+    public void scan (Pattern pattern, ClassLoader loader, boolean isNullInclusive, boolean visitParent)
+    throws Exception
+    {
+        while (loader!=null)
+        {
+            if (loader instanceof URLClassLoader)
+            {
+                URL[] urls = ((URLClassLoader)loader).getURLs();
+                if (urls != null)
+                {
+                    URI[] uris = new URI[urls.length];
+                    int i=0;
+                    for (URL u : urls)
+                        uris[i++] = u.toURI();
+                    scan (pattern, uris, isNullInclusive);
+                }
+            }     
+            if (visitParent)
+                loader=loader.getParent();
+            else
+                loader = null;
+        }  
+    }
+    
+    
+    public void matched (URI uri)
+    throws Exception
+    {
+        LOG.debug("Search of {}",uri);
+        if (uri.toString().toLowerCase(Locale.ENGLISH).endsWith(".jar"))
+        {
+         
+            InputStream in = Resource.newResource(uri).getInputStream();
+            if (in==null)
+                return;
+
+            JarInputStream jar_in = new JarInputStream(in);
+            try
+            { 
+                JarEntry entry = jar_in.getNextJarEntry();
+                while (entry!=null)
+                {
+                    processEntry(uri, entry);
+                    entry = jar_in.getNextJarEntry();
+                }
+            }
+            finally
+            {
+                jar_in.close();
+            }   
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/src/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
new file mode 100644
index 0000000..7a62627
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java
@@ -0,0 +1,146 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+/**
+ * 
+ * JettyWebConfiguration.
+ * 
+ * Looks for Xmlconfiguration files in WEB-INF.  Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml
+ *
+ * 
+ *
+ */
+public class JettyWebXmlConfiguration extends AbstractConfiguration
+{
+    private static final Logger LOG = Log.getLogger(JettyWebXmlConfiguration.class);
+
+    /** The value of this property points to the WEB-INF directory of
+     * the web-app currently installed.
+     * it is passed as a property to the jetty-web.xml file */
+    public static final String PROPERTY_THIS_WEB_INF_URL = "this.web-inf.url";
+
+
+    public static final String XML_CONFIGURATION = "org.eclipse.jetty.webapp.JettyWebXmlConfiguration";
+    public static final String JETTY_WEB_XML = "jetty-web.xml";
+    
+    /** 
+     * Configure
+     * Apply web-jetty.xml configuration
+     * @see Configuration#configure(WebAppContext)
+     */
+    @Override
+    public void configure (WebAppContext context) throws Exception
+    {
+        //cannot configure if the _context is already started
+        if (context.isStarted())
+        {
+            LOG.debug("Cannot configure webapp after it is started");
+            return;
+        }
+        
+        LOG.debug("Configuring web-jetty.xml");
+        
+        Resource web_inf = context.getWebInf();
+        // handle any WEB-INF descriptors
+        if(web_inf!=null&&web_inf.isDirectory())
+        {
+            // do jetty.xml file
+            Resource jetty=web_inf.addPath("jetty8-web.xml");
+            if(!jetty.exists())
+                jetty=web_inf.addPath(JETTY_WEB_XML);
+            if(!jetty.exists())
+                jetty=web_inf.addPath("web-jetty.xml");
+
+            if(jetty.exists())
+            {
+                // No server classes while configuring 
+                String[] old_server_classes = context.getServerClasses();
+                try
+                {
+                    context.setServerClasses(null);
+                    if(LOG.isDebugEnabled())
+                        LOG.debug("Configure: "+jetty);
+                    
+                    XmlConfiguration jetty_config = (XmlConfiguration)context.getAttribute(XML_CONFIGURATION);
+                    
+                    if (jetty_config==null)
+                    {
+                        jetty_config=new XmlConfiguration(jetty.getURL());
+                    }
+                    else
+                    {
+                        context.removeAttribute(XML_CONFIGURATION);
+                    }
+                    setupXmlConfiguration(context,jetty_config, web_inf);
+                    try
+                    {
+                        jetty_config.configure(context);
+                    }
+                    catch (ClassNotFoundException e)
+                    {
+                        LOG.warn("Unable to process jetty-web.xml", e);
+                    }
+                }
+                finally
+                {
+                    if (context.getServerClasses()==null)
+                        context.setServerClasses(old_server_classes);
+                }
+            }
+        }
+    }
+
+    /**
+     * Configures some well-known properties before the XmlConfiguration reads
+     * the configuration.
+     * @param jetty_config The configuration object.
+     */
+    private void setupXmlConfiguration(WebAppContext context, XmlConfiguration jetty_config, Resource web_inf)
+    {
+        setupXmlConfiguration(jetty_config,web_inf);
+    }
+    
+    /**
+     * Configures some well-known properties before the XmlConfiguration reads
+     * the configuration.
+     * @param jetty_config The configuration object.
+     */
+    private void setupXmlConfiguration(XmlConfiguration jetty_config, Resource web_inf)
+    {
+    	Map<String,String> props = jetty_config.getProperties();
+    	if (props == null)
+    	{
+    		props = new HashMap<String, String>();
+    		jetty_config.setProperties(props);
+    	}
+    	
+    	// TODO - should this be an id rather than a property?
+    	props.put(PROPERTY_THIS_WEB_INF_URL, String.valueOf(web_inf.getURL()));
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/MetaData.java b/src/java/org/eclipse/jetty/webapp/MetaData.java
new file mode 100644
index 0000000..f946c59
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/MetaData.java
@@ -0,0 +1,585 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+
+
+/**
+ * MetaData
+ *
+ * All data associated with the configuration and deployment of a web application.
+ */
+public class MetaData
+{
+    private static final Logger LOG = Log.getLogger(MetaData.class);
+        
+    public static final String ORDERED_LIBS = "javax.servlet.context.orderedLibs";
+
+    protected Map<String, OriginInfo> _origins  =new HashMap<String,OriginInfo>();
+    protected WebDescriptor _webDefaultsRoot;
+    protected WebDescriptor _webXmlRoot;
+    protected final List<WebDescriptor> _webOverrideRoots=new ArrayList<WebDescriptor>();
+    protected boolean _metaDataComplete;
+    protected final List<DiscoveredAnnotation> _annotations = new ArrayList<DiscoveredAnnotation>();
+    protected final List<DescriptorProcessor> _descriptorProcessors = new ArrayList<DescriptorProcessor>();
+    protected final List<FragmentDescriptor> _webFragmentRoots = new ArrayList<FragmentDescriptor>();
+    protected final Map<String,FragmentDescriptor> _webFragmentNameMap = new HashMap<String,FragmentDescriptor>();
+    protected final Map<Resource, FragmentDescriptor> _webFragmentResourceMap = new HashMap<Resource, FragmentDescriptor>();
+    protected final Map<Resource, List<DiscoveredAnnotation>> _webFragmentAnnotations = new HashMap<Resource, List<DiscoveredAnnotation>>();
+    protected final List<Resource> _webInfJars = new ArrayList<Resource>();
+    protected final List<Resource> _orderedWebInfJars = new ArrayList<Resource>(); 
+    protected final List<Resource> _orderedContainerJars = new ArrayList<Resource>();
+    protected Ordering _ordering;//can be set to RelativeOrdering by web-default.xml, web.xml, web-override.xml
+    protected boolean allowDuplicateFragmentNames = false;
+   
+ 
+    
+  
+
+    public static class OriginInfo
+    {   
+        protected String name;
+        protected Origin origin;
+        protected Descriptor descriptor;
+        
+        public OriginInfo (String n, Descriptor d)
+        {
+            name = n;
+            descriptor = d;           
+            if (d == null)
+                throw new IllegalArgumentException("No descriptor");
+            if (d instanceof FragmentDescriptor)
+                origin = Origin.WebFragment;
+            else if (d instanceof OverrideDescriptor)
+                origin =  Origin.WebOverride;
+            else if (d instanceof DefaultsDescriptor)
+                origin =  Origin.WebDefaults;
+            else
+                origin = Origin.WebXml;
+        }
+        
+        public OriginInfo (String n)
+        {
+            name = n;
+            origin = Origin.Annotation;
+        }
+        
+        public OriginInfo(String n, Origin o)
+        {
+            name = n;
+            origin = o;
+        }
+        
+        public String getName()
+        {
+            return name;
+        }
+        
+        public Origin getOriginType()
+        {
+            return origin;
+        }
+        
+        public Descriptor getDescriptor()
+        {
+            return descriptor;
+        }
+    }
+   
+    public MetaData ()
+    {
+    }
+    
+    /**
+     * Empty ready for reuse
+     */
+    public void clear ()
+    {
+        _webDefaultsRoot = null;
+        _origins.clear();
+        _webXmlRoot = null;
+        _webOverrideRoots.clear();
+        _metaDataComplete = false;
+        _annotations.clear();
+        _descriptorProcessors.clear();
+        _webFragmentRoots.clear();
+        _webFragmentNameMap.clear();
+        _webFragmentResourceMap.clear();
+        _webFragmentAnnotations.clear();
+        _webInfJars.clear();
+        _orderedWebInfJars.clear();
+        _orderedContainerJars.clear();
+        _ordering = null;
+        allowDuplicateFragmentNames = false;
+    }
+    
+    public void setDefaults (Resource webDefaults)
+    throws Exception
+    {
+        _webDefaultsRoot =  new DefaultsDescriptor(webDefaults); 
+        _webDefaultsRoot.parse();
+        if (_webDefaultsRoot.isOrdered())
+        {
+            if (_ordering == null)
+                _ordering = new Ordering.AbsoluteOrdering(this);
+
+            List<String> order = _webDefaultsRoot.getOrdering();
+            for (String s:order)
+            {
+                if (s.equalsIgnoreCase("others"))
+                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
+                else 
+                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+            }
+        }    
+    }
+    
+    public void setWebXml (Resource webXml)
+    throws Exception
+    {
+        _webXmlRoot = new WebDescriptor(webXml);
+        _webXmlRoot.parse();
+        _metaDataComplete=_webXmlRoot.getMetaDataComplete() == MetaDataComplete.True;
+        
+        
+        
+        if (_webXmlRoot.isOrdered())
+        {
+            if (_ordering == null)
+                _ordering = new Ordering.AbsoluteOrdering(this);
+
+            List<String> order = _webXmlRoot.getOrdering();
+            for (String s:order)
+            {
+                if (s.equalsIgnoreCase("others"))
+                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
+                else 
+                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+            }
+        }    
+    }
+    
+    public void addOverride (Resource override)
+    throws Exception
+    {
+        OverrideDescriptor webOverrideRoot = new OverrideDescriptor(override);
+        webOverrideRoot.setValidating(false);
+        webOverrideRoot.parse();
+        
+        switch(webOverrideRoot.getMetaDataComplete())
+        {
+            case True:
+                _metaDataComplete=true;
+                break;
+            case False:
+                _metaDataComplete=false;
+                break;
+            case NotSet:
+                break;
+        }
+        
+        if (webOverrideRoot.isOrdered())
+        {
+            if (_ordering == null)
+                _ordering = new Ordering.AbsoluteOrdering(this);
+
+            List<String> order = webOverrideRoot.getOrdering();
+            for (String s:order)
+            {
+                if (s.equalsIgnoreCase("others"))
+                    ((Ordering.AbsoluteOrdering)_ordering).addOthers();
+                else 
+                    ((Ordering.AbsoluteOrdering)_ordering).add(s);
+            }
+        }   
+        _webOverrideRoots.add(webOverrideRoot);
+    }
+    
+    
+    /**
+     * Add a web-fragment.xml
+     * 
+     * @param jarResource the jar the fragment is contained in
+     * @param xmlResource the resource representing the xml file
+     * @throws Exception
+     */
+    public void addFragment (Resource jarResource, Resource xmlResource)
+    throws Exception
+    { 
+        if (_metaDataComplete)
+            return; //do not process anything else if web.xml/web-override.xml set metadata-complete
+        
+        //Metadata-complete is not set, or there is no web.xml
+        FragmentDescriptor descriptor = new FragmentDescriptor(xmlResource);
+        _webFragmentResourceMap.put(jarResource, descriptor);
+        _webFragmentRoots.add(descriptor);
+        
+        descriptor.parse();
+        
+        if (descriptor.getName() != null)
+        {
+            Descriptor existing = _webFragmentNameMap.get(descriptor.getName());
+            if (existing != null && !isAllowDuplicateFragmentNames())
+            {
+                throw new IllegalStateException("Duplicate fragment name: "+descriptor.getName()+" for "+existing.getResource()+" and "+descriptor.getResource());
+            }
+            else
+                _webFragmentNameMap.put(descriptor.getName(), descriptor);
+        }
+
+        //If web.xml has specified an absolute ordering, ignore any relative ordering in the fragment
+        if (_ordering != null && _ordering.isAbsolute())
+            return;
+        
+        if (_ordering == null && descriptor.isOrdered())
+            _ordering = new Ordering.RelativeOrdering(this);
+    }
+
+    /**
+     * Annotations not associated with a WEB-INF/lib fragment jar.
+     * These are from WEB-INF/classes or the ??container path??
+     * @param annotations
+     */
+    public void addDiscoveredAnnotations(List<DiscoveredAnnotation> annotations)
+    {
+        if (annotations == null)
+            return;
+        for (DiscoveredAnnotation a:annotations)
+        {
+            Resource r = a.getResource();
+            if (r == null || !_webInfJars.contains(r))
+                _annotations.add(a);
+            else 
+                addDiscoveredAnnotation(a.getResource(), a);
+                
+        }
+    }
+    
+    
+    public void addDiscoveredAnnotation(Resource resource, DiscoveredAnnotation annotation)
+    {
+        List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
+        if (list == null)
+        {
+            list = new ArrayList<DiscoveredAnnotation>();
+            _webFragmentAnnotations.put(resource, list);
+        }
+        list.add(annotation);
+    }
+    
+
+    public void addDiscoveredAnnotations(Resource resource, List<DiscoveredAnnotation> annotations)
+    {
+        List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
+        if (list == null)
+        {
+            list = new ArrayList<DiscoveredAnnotation>();
+            _webFragmentAnnotations.put(resource, list);
+        }
+            
+        list.addAll(annotations);
+    }
+    
+    public void addDescriptorProcessor(DescriptorProcessor p)
+    {
+        _descriptorProcessors.add(p);
+    }
+    
+    public void orderFragments ()
+    {
+        //if we have already ordered them don't do it again
+        if (_orderedWebInfJars.size()==_webInfJars.size())
+            return;
+        
+        if (_ordering != null)
+            _orderedWebInfJars.addAll(_ordering.order(_webInfJars));
+        else
+            _orderedWebInfJars.addAll(_webInfJars);
+    }
+    
+    
+    /**
+     * Resolve all servlet/filter/listener metadata from all sources: descriptors and annotations.
+     * 
+     */
+    public void resolve (WebAppContext context)
+    throws Exception
+    {
+        LOG.debug("metadata resolve {}",context);
+        
+        //Ensure origins is fresh
+        _origins.clear();
+        
+        // Set the ordered lib attribute
+        if (_ordering != null)
+        {
+            List<String> orderedLibs = new ArrayList<String>();
+            for (Resource webInfJar:_orderedWebInfJars)
+            {
+                //get just the name of the jar file
+                String fullname = webInfJar.getName();
+                int i = fullname.indexOf(".jar");          
+                int j = fullname.lastIndexOf("/", i);
+                orderedLibs.add(fullname.substring(j+1,i+4));
+            }
+            context.setAttribute(ServletContext.ORDERED_LIBS, orderedLibs);
+        }
+
+        // set the webxml version
+        if (_webXmlRoot != null)
+        {
+            context.getServletContext().setEffectiveMajorVersion(_webXmlRoot.getMajorVersion());
+            context.getServletContext().setEffectiveMinorVersion(_webXmlRoot.getMinorVersion());
+        }
+
+        for (DescriptorProcessor p:_descriptorProcessors)
+        {
+            p.process(context,getWebDefault());
+            p.process(context,getWebXml());
+            for (WebDescriptor wd : getOverrideWebs())   
+            {
+                LOG.debug("process {} {}",context,wd);
+                p.process(context,wd);
+            }
+        }
+        
+        for (DiscoveredAnnotation a:_annotations)
+        {
+            LOG.debug("apply {}",a);
+            a.apply();
+        }
+    
+        
+        List<Resource> resources = getOrderedWebInfJars();
+        for (Resource r:resources)
+        {
+            FragmentDescriptor fd = _webFragmentResourceMap.get(r);
+            if (fd != null)
+            {
+                for (DescriptorProcessor p:_descriptorProcessors)
+                {
+                    LOG.debug("process {} {}",context,fd);
+                    p.process(context,fd);
+                }
+            }
+            
+            List<DiscoveredAnnotation> fragAnnotations = _webFragmentAnnotations.get(r);
+            if (fragAnnotations != null)
+            {
+                for (DiscoveredAnnotation a:fragAnnotations)
+                {
+                    LOG.debug("apply {}",a);
+                    a.apply();
+                }
+            }
+        }
+        
+    }
+    
+    public boolean isDistributable ()
+    {
+        boolean distributable = (
+                (_webDefaultsRoot != null && _webDefaultsRoot.isDistributable()) 
+                || (_webXmlRoot != null && _webXmlRoot.isDistributable()));
+        
+        for (WebDescriptor d : _webOverrideRoots)
+            distributable&=d.isDistributable();
+        
+        List<Resource> orderedResources = getOrderedWebInfJars();
+        for (Resource r: orderedResources)
+        {  
+            FragmentDescriptor d = _webFragmentResourceMap.get(r);
+            if (d!=null)
+                distributable = distributable && d.isDistributable();
+        }
+        return distributable;
+    }
+   
+    
+    public WebDescriptor getWebXml ()
+    {
+        return _webXmlRoot;
+    }
+    
+    public List<WebDescriptor> getOverrideWebs ()
+    {
+        return _webOverrideRoots;
+    }
+    
+    public WebDescriptor getWebDefault ()
+    {
+        return _webDefaultsRoot;
+    }
+    
+    public List<FragmentDescriptor> getFragments ()
+    {
+        return _webFragmentRoots;
+    }
+    
+    public List<Resource> getOrderedWebInfJars()
+    {
+        return _orderedWebInfJars == null? new ArrayList<Resource>(): _orderedWebInfJars;
+    }
+    
+    public List<FragmentDescriptor> getOrderedFragments ()
+    {
+        List<FragmentDescriptor> list = new ArrayList<FragmentDescriptor>();
+        if (_orderedWebInfJars == null)
+            return list;
+
+        for (Resource r:_orderedWebInfJars)
+        {
+            FragmentDescriptor fd = _webFragmentResourceMap.get(r);
+            if (fd != null)
+                list.add(fd);
+        }
+        return list;
+    }
+    
+    public Ordering getOrdering()
+    {
+        return _ordering;
+    }
+    
+    public void setOrdering (Ordering o)
+    {
+        _ordering = o;
+    }
+    
+    public FragmentDescriptor getFragment (Resource jar)
+    {
+        return _webFragmentResourceMap.get(jar);
+    }
+    
+    public FragmentDescriptor getFragment(String name)
+    {
+        return _webFragmentNameMap.get(name);
+    }
+    
+    public Resource getJarForFragment (String name)
+    {
+        FragmentDescriptor f = getFragment(name);
+        if (f == null)
+            return null;
+        
+        Resource jar = null;
+        for (Resource r: _webFragmentResourceMap.keySet())
+        {
+            if (_webFragmentResourceMap.get(r).equals(f))
+                jar = r;
+        }
+        return jar;
+    }
+    
+    public Map<String,FragmentDescriptor> getNamedFragments ()
+    {
+        return Collections.unmodifiableMap(_webFragmentNameMap);
+    }
+    
+    
+    public Origin getOrigin (String name)
+    {
+        OriginInfo x =  _origins.get(name);
+        if (x == null)
+            return Origin.NotSet;
+        
+        return x.getOriginType();
+    }
+  
+ 
+    public Descriptor getOriginDescriptor (String name)
+    {
+        OriginInfo o = _origins.get(name);
+        if (o == null)
+            return null;
+        return o.getDescriptor();
+    }
+    
+    public void setOrigin (String name, Descriptor d)
+    {
+        OriginInfo x = new OriginInfo (name, d);
+        _origins.put(name, x);
+    }
+    
+    public void setOrigin (String name)
+    {
+        if (name == null)
+            return;
+       
+        OriginInfo x = new OriginInfo (name, Origin.Annotation);
+        _origins.put(name, x);
+    }
+    
+    public void setOrigin(String name, Origin origin)
+    {
+        if (name == null)
+            return;
+       
+        OriginInfo x = new OriginInfo (name, origin);
+        _origins.put(name, x);
+    }
+
+    public boolean isMetaDataComplete()
+    {
+        return _metaDataComplete;
+    }
+
+    
+    public void addWebInfJar(Resource newResource)
+    {
+        _webInfJars.add(newResource);
+    }
+
+    public List<Resource> getWebInfJars()
+    {
+        return Collections.unmodifiableList(_webInfJars);
+    }
+    
+    public List<Resource> getOrderedContainerJars()
+    {
+        return _orderedContainerJars;
+    }
+    
+    public void addContainerJar(Resource jar)
+    {
+        _orderedContainerJars.add(jar);
+    }
+    public boolean isAllowDuplicateFragmentNames()
+    {
+        return allowDuplicateFragmentNames;
+    }
+
+    public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
+    {
+        this.allowDuplicateFragmentNames = allowDuplicateFragmentNames;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/MetaDataComplete.java b/src/java/org/eclipse/jetty/webapp/MetaDataComplete.java
new file mode 100644
index 0000000..c199f68
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/MetaDataComplete.java
@@ -0,0 +1,21 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+public enum MetaDataComplete {NotSet, True, False}
diff --git a/src/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java b/src/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
new file mode 100644
index 0000000..dca8da1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/MetaInfConfiguration.java
@@ -0,0 +1,152 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.jar.JarEntry;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * MetaInfConfiguration
+ *
+ * Scan META-INF of all jars in WEB-INF/lib to find:
+ * <ul>
+ * <li>tlds
+ * <li>web-fragment.xml
+ * <li>resources
+ * </ul>
+ */
+public class MetaInfConfiguration extends AbstractConfiguration
+{
+    private static final Logger LOG = Log.getLogger(MetaInfConfiguration.class);
+
+    public static final String METAINF_TLDS = TagLibConfiguration.TLD_RESOURCES;
+    public static final String METAINF_FRAGMENTS = FragmentConfiguration.FRAGMENT_RESOURCES;
+    public static final String METAINF_RESOURCES = WebInfConfiguration.RESOURCE_URLS;
+  
+    @Override
+    public void preConfigure(final WebAppContext context) throws Exception
+    {
+       //Merge all container and webinf lib jars to look for META-INF resources
+      
+        ArrayList<Resource> jars = new ArrayList<Resource>();
+        jars.addAll(context.getMetaData().getOrderedContainerJars());
+        jars.addAll(context.getMetaData().getWebInfJars());
+        
+        JarScanner scanner = new JarScanner()
+        {
+            public void processEntry(URI jarUri, JarEntry entry)
+            {
+                try
+                {
+                    MetaInfConfiguration.this.processEntry(context,jarUri,entry);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Problem processing jar entry " + entry, e);
+                }
+            }
+        };
+        
+        
+        //Scan jars for META-INF information
+        if (jars != null)
+        {
+            URI[] uris = new URI[jars.size()];
+            int i=0;
+            for (Resource r : jars)
+            {
+                uris[i++] = r.getURI();
+            }
+            scanner.scan(null, uris, true);
+        }
+    }
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    {
+        
+    }
+
+    @Override
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+ 
+    }
+
+    @Override
+    public void postConfigure(WebAppContext context) throws Exception
+    {
+        context.setAttribute(METAINF_FRAGMENTS, null); 
+        context.setAttribute(METAINF_RESOURCES, null);
+        context.setAttribute(METAINF_TLDS, null);
+    }
+
+    public void addResource (WebAppContext context, String attribute, Resource jar)
+    {
+        @SuppressWarnings("unchecked")
+        List<Resource> list = (List<Resource>)context.getAttribute(attribute);
+        if (list==null)
+        {
+            list=new ArrayList<Resource>();
+            context.setAttribute(attribute,list);
+        }
+        if (!list.contains(jar))
+            list.add(jar);
+    }
+    
+    
+    protected void processEntry(WebAppContext context, URI jarUri, JarEntry entry)
+    {
+        String name = entry.getName();
+
+        if (!name.startsWith("META-INF/"))
+            return;
+        
+        try
+        {
+            if (name.equals("META-INF/web-fragment.xml") && context.isConfigurationDiscovered())
+            {
+                addResource(context,METAINF_FRAGMENTS,Resource.newResource(jarUri));     
+            }
+            else if (name.equals("META-INF/resources/") && context.isConfigurationDiscovered())
+            {
+                addResource(context,METAINF_RESOURCES,Resource.newResource("jar:"+jarUri+"!/META-INF/resources"));
+            }
+            else
+            {
+                String lcname = name.toLowerCase(Locale.ENGLISH);
+                if (lcname.endsWith(".tld"))
+                {
+                    addResource(context,METAINF_TLDS,Resource.newResource("jar:"+jarUri+"!/"+name));
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            context.getServletContext().log(jarUri+"!/"+name,e);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/Ordering.java b/src/java/org/eclipse/jetty/webapp/Ordering.java
new file mode 100644
index 0000000..480fc30
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/Ordering.java
@@ -0,0 +1,491 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/**
+ * Ordering
+ *
+ * Ordering options for jars in WEB-INF lib.
+ */
+public interface Ordering
+{  
+
+    public List<Resource> order(List<Resource> fragments);
+    public boolean isAbsolute ();
+    public boolean hasOther();
+
+
+    /**
+     * AbsoluteOrdering
+     *
+     * An &lt;absolute-order&gt; element in web.xml
+     */
+    public static class AbsoluteOrdering implements Ordering
+    {
+        public static final String OTHER = "@@-OTHER-@@";
+        protected List<String> _order = new ArrayList<String>();
+        protected boolean _hasOther = false;
+        protected MetaData _metaData;
+    
+        public AbsoluteOrdering (MetaData metaData)
+        {
+            _metaData = metaData;
+        }
+        
+        /** 
+         * Order the list of jars in WEB-INF/lib according to the ordering declarations in the descriptors
+         * @see org.eclipse.jetty.webapp.Ordering#order(java.util.List)
+         */
+        @Override
+        public List<Resource> order(List<Resource> jars)
+        {           
+            List<Resource> orderedList = new ArrayList<Resource>();
+            List<Resource> tmp = new ArrayList<Resource>(jars);
+          
+            //1. put everything into the list of named others, and take the named ones out of there,
+            //assuming we will want to use the <other> clause
+            Map<String,FragmentDescriptor> others = new HashMap<String,FragmentDescriptor>(_metaData.getNamedFragments());
+            
+            //2. for each name, take out of the list of others, add to tail of list
+            int index = -1;
+            for (String item:_order)
+            {
+                if (!item.equals(OTHER))
+                {
+                    FragmentDescriptor f = others.remove(item);
+                    if (f != null)
+                    {
+                        Resource jar = _metaData.getJarForFragment(item);
+                        orderedList.add(jar); //take from others and put into final list in order, ignoring duplicate names
+                        //remove resource from list for resource matching name of descriptor
+                        tmp.remove(jar);
+                    }
+                }
+                else
+                    index = orderedList.size(); //remember the index at which we want to add in all the others
+            }
+            
+            //3. if <other> was specified, insert rest of the fragments 
+            if (_hasOther)
+            {
+                orderedList.addAll((index < 0? 0: index), tmp);
+            }
+            
+            return orderedList;
+        }
+        
+        @Override
+        public boolean isAbsolute()
+        {
+            return true;
+        }
+        
+        public void add (String name)
+        {
+            _order.add(name); 
+        }
+        
+        public void addOthers ()
+        {
+            if (_hasOther)
+                throw new IllegalStateException ("Duplicate <other> element in absolute ordering");
+            
+            _hasOther = true;
+            _order.add(OTHER);
+        }
+        
+        @Override
+        public boolean hasOther ()
+        {
+            return _hasOther;
+        }
+    }
+    /**
+     * RelativeOrdering
+     *
+     * A set of &lt;order&gt; elements in web-fragment.xmls.
+     */
+    public static class RelativeOrdering implements Ordering
+    {
+        protected MetaData _metaData;
+        protected LinkedList<Resource> _beforeOthers = new LinkedList<Resource>();
+        protected LinkedList<Resource> _afterOthers = new LinkedList<Resource>();
+        protected LinkedList<Resource> _noOthers = new LinkedList<Resource>();
+        
+        public RelativeOrdering (MetaData metaData)
+        {
+            _metaData = metaData;
+        }
+        /** 
+         * Order the list of jars according to the ordering declared
+         * in the various web-fragment.xml files.
+         * @see org.eclipse.jetty.webapp.Ordering#order(java.util.List)
+         */
+        @Override
+        public List<Resource> order(List<Resource> jars)
+        {         
+            //for each jar, put it into the ordering according to the fragment ordering
+            for (Resource jar:jars)
+            {
+                //check if the jar has a fragment descriptor
+                FragmentDescriptor descriptor = _metaData.getFragment(jar);
+                if (descriptor != null)
+                {
+                    switch (descriptor.getOtherType())
+                    {
+                        case None:
+                        {
+                            ((RelativeOrdering)_metaData.getOrdering()).addNoOthers(jar);
+                            break;
+                        }
+                        case Before:
+                        { 
+                            ((RelativeOrdering)_metaData.getOrdering()).addBeforeOthers(jar);
+                            break;
+                        }
+                        case After:
+                        {
+                            ((RelativeOrdering)_metaData.getOrdering()).addAfterOthers(jar);
+                            break;
+                        }
+                    } 
+                }
+                else
+                {
+                    //jar fragment has no descriptor, but there is a relative ordering in place, so it must be part of the others
+                    ((RelativeOrdering)_metaData.getOrdering()).addNoOthers(jar);
+                }
+            }            
+                
+            //now apply the ordering
+            List<Resource> orderedList = new ArrayList<Resource>(); 
+            int maxIterations = 2;
+            boolean done = false;
+            do
+            {
+                //1. order the before-others according to any explicit before/after relationships 
+                boolean changesBefore = orderList(_beforeOthers);
+    
+                //2. order the after-others according to any explicit before/after relationships
+                boolean changesAfter = orderList(_afterOthers);
+    
+                //3. order the no-others according to their explicit before/after relationships
+                boolean changesNone = orderList(_noOthers);
+                
+                //we're finished on a clean pass through with no ordering changes
+                done = (!changesBefore && !changesAfter && !changesNone);
+            }
+            while (!done && (--maxIterations >0));
+            
+            //4. merge before-others + no-others +after-others
+            if (!done)
+                throw new IllegalStateException("Circular references for fragments");
+            
+            for (Resource r: _beforeOthers)
+                orderedList.add(r);
+            for (Resource r: _noOthers)
+                orderedList.add(r);
+            for(Resource r: _afterOthers)
+                orderedList.add(r);
+            
+            return orderedList;
+        }
+        
+        @Override
+        public boolean isAbsolute ()
+        {
+            return false;
+        }
+        
+        @Override
+        public boolean hasOther ()
+        {
+            return !_beforeOthers.isEmpty() || !_afterOthers.isEmpty();
+        }
+        
+        public void addBeforeOthers (Resource r)
+        {
+            _beforeOthers.addLast(r);
+        }
+        
+        public void addAfterOthers (Resource r)
+        {
+            _afterOthers.addLast(r);
+        }
+        
+        public void addNoOthers (Resource r)
+        {
+            _noOthers.addLast(r);
+        }
+        
+       protected boolean orderList (LinkedList<Resource> list)
+       {
+           //Take a copy of the list so we can iterate over it and at the same time do random insertions
+           boolean changes = false;
+           List<Resource> iterable = new ArrayList<Resource>(list);
+           Iterator<Resource> itor = iterable.iterator();
+           
+           while (itor.hasNext())
+           {
+               Resource r = itor.next();
+               FragmentDescriptor f = _metaData.getFragment(r);
+               if (f == null)
+               {
+                   //no fragment for this resource so cannot have any ordering directives
+                   continue;
+               }
+                
+               //Handle any explicit <before> relationships for the fragment we're considering
+               List<String> befores = f.getBefores();
+               if (befores != null && !befores.isEmpty())
+               {
+                   for (String b: befores)
+                   {
+                       //Fragment we're considering must be before b
+                       //Check that we are already before it, if not, move us so that we are.
+                       //If the name does not exist in our list, then get it out of the no-other list
+                       if (!isBefore(list, f.getName(), b))
+                       {
+                           //b is not already before name, move it so that it is
+                           int idx1 = getIndexOf(list, f.getName());
+                           int idx2 = getIndexOf(list, b);
+    
+                           //if b is not in the same list
+                           if (idx2 < 0)
+                           {
+                               changes = true;
+                               // must be in the noOthers list or it would have been an error
+                               Resource bResource = _metaData.getJarForFragment(b);
+                               if (bResource != null)
+                               {
+                                   //If its in the no-others list, insert into this list so that we are before it
+                                   if (_noOthers.remove(bResource))
+                                   {
+                                       insert(list, idx1+1, b);
+                                      
+                                   }
+                               }
+                           }
+                           else
+                           {
+                               //b is in the same list but b is before name, so swap it around
+                               list.remove(idx1);
+                               insert(list, idx2, f.getName());
+                               changes = true;
+                           }
+                       }
+                   }
+               }
+    
+               //Handle any explicit <after> relationships
+               List<String> afters = f.getAfters();
+               if (afters != null && !afters.isEmpty())
+               {
+                   for (String a: afters)
+                   {
+                       //Check that fragment we're considering is after a, moving it if possible if its not
+                       if (!isAfter(list, f.getName(), a))
+                       {
+                           //name is not after a, move it
+                           int idx1 = getIndexOf(list, f.getName());
+                           int idx2 = getIndexOf(list, a);
+                           
+                           //if a is not in the same list as name
+                           if (idx2 < 0)
+                           {
+                               changes = true;
+                               //take it out of the noOthers list and put it in the right place in this list
+                               Resource aResource = _metaData.getJarForFragment(a);
+                               if (aResource != null)
+                               {
+                                   if (_noOthers.remove(aResource))
+                                   {
+                                       insert(list,idx1, aResource);       
+                                   }
+                               }
+                           }
+                           else
+                           {
+                               //a is in the same list as name, but in the wrong place, so move it
+                               list.remove(idx2);
+                               insert(list,idx1, a);
+                               changes = true;
+                           }
+                       }
+                       //Name we're considering must be after this name
+                       //Check we're already after it, if not, move us so that we are.
+                       //If the name does not exist in our list, then get it out of the no-other list
+                   }
+               }
+           }
+    
+           return changes;
+       }
+    
+       /**
+        * Is fragment with name a before fragment with name b?
+        * @param list
+        * @param fragNameA
+        * @param fragNameB
+        * @return true if fragment name A is before fragment name B 
+        */
+       protected boolean isBefore (List<Resource> list, String fragNameA, String fragNameB)
+       {
+           //check if a and b are already in the same list, and b is already
+           //before a 
+           int idxa = getIndexOf(list, fragNameA);
+           int idxb = getIndexOf(list, fragNameB);
+           
+           
+           if (idxb >=0 && idxb < idxa)
+           {
+               //a and b are in the same list but a is not before b
+               return false;
+           }
+           
+           if (idxb < 0)
+           {
+               //a and b are not in the same list, but it is still possible that a is before
+               //b, depending on which list we're examining
+               if (list == _beforeOthers)
+               {
+                   //The list we're looking at is the beforeOthers.If b is in the _afterOthers or the _noOthers, then by
+                   //definition a is before it
+                   return true;
+               }
+               else if (list == _afterOthers)
+               {
+                   //The list we're looking at is the afterOthers, then a will be the tail of
+                   //the final list.  If b is in the beforeOthers list, then b will be before a and an error.
+                   if (_beforeOthers.contains(fragNameB))
+                       throw new IllegalStateException("Incorrect relationship: "+fragNameA+" before "+fragNameB);
+                   else
+                       return false; //b could be moved to the list
+               }
+           }
+          
+           //a and b are in the same list and a is already before b
+           return true;
+       }
+    
+    
+       /**
+        * Is fragment name "a" after fragment name "b"?
+        * @param list
+        * @param fragNameA
+        * @param fragNameB
+        * @return true if fragment name A is after fragment name B
+        */
+       protected boolean isAfter(List<Resource> list, String fragNameA, String fragNameB)
+       {
+           int idxa = getIndexOf(list, fragNameA);
+           int idxb = getIndexOf(list, fragNameB);
+           
+           if (idxb >=0 && idxa < idxb)
+           {
+               //a and b are both in the same list, but a is before b
+               return false;
+           }
+           
+           if (idxb < 0)
+           {
+               //a and b are in different lists. a could still be after b depending on which list it is in.
+    
+               if (list == _afterOthers)
+               {
+                   //The list we're looking at is the afterOthers. If b is in the beforeOthers or noOthers then
+                   //by definition a is after b because a is in the afterOthers list.
+                   return true;
+               }
+               else if (list == _beforeOthers)
+               {
+                   //The list we're looking at is beforeOthers, and contains a and will be before
+                   //everything else in the final ist. If b is in the afterOthers list, then a cannot be before b.
+                   if (_afterOthers.contains(fragNameB))
+                       throw new IllegalStateException("Incorrect relationship: "+fragNameB+" after "+fragNameA);
+                   else
+                       return false; //b could be moved from noOthers list
+               }
+           }
+    
+           return true; //a and b in the same list, a is after b
+       }
+    
+       /**
+        * Insert the resource matching the fragName into the list of resources
+        * at the location indicated by index.
+        * 
+        * @param list
+        * @param index
+        * @param fragName
+        */
+       protected void insert(List<Resource> list, int index, String fragName)
+       {
+           Resource jar = _metaData.getJarForFragment(fragName);
+           if (jar == null)
+               throw new IllegalStateException("No jar for insertion");
+           
+           insert(list, index, jar);
+       }
+    
+       protected void insert(List<Resource> list, int index, Resource resource)
+       {
+           if (list == null)
+               throw new IllegalStateException("List is null for insertion");
+           
+           //add it at the end
+           if (index > list.size())
+               list.add(resource);
+           else
+               list.add(index, resource);
+       }
+    
+       protected void remove (List<Resource> resources, Resource r)
+       {
+           if (resources == null)
+               return;
+           resources.remove(r);
+       }
+    
+       protected int getIndexOf(List<Resource> resources, String fragmentName)
+       {
+          FragmentDescriptor fd = _metaData.getFragment(fragmentName);
+          if (fd == null)
+              return -1;
+          
+          
+          Resource r = _metaData.getJarForFragment(fragmentName);
+          if (r == null)
+              return -1;
+          
+          return resources.indexOf(r);
+       }
+    }
+  
+}
diff --git a/src/java/org/eclipse/jetty/webapp/Origin.java b/src/java/org/eclipse/jetty/webapp/Origin.java
new file mode 100644
index 0000000..dbd70b1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/Origin.java
@@ -0,0 +1,21 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+public enum Origin {NotSet, WebXml, WebDefaults, WebOverride, WebFragment, Annotation, API}
diff --git a/src/java/org/eclipse/jetty/webapp/OverrideDescriptor.java b/src/java/org/eclipse/jetty/webapp/OverrideDescriptor.java
new file mode 100644
index 0000000..b67fe5d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/OverrideDescriptor.java
@@ -0,0 +1,34 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * OverrideDescriptor
+ *
+ *
+ */
+public class OverrideDescriptor extends WebDescriptor
+{
+    public OverrideDescriptor(Resource xml)
+    {
+        super(xml);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/src/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
new file mode 100644
index 0000000..43ce722
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
@@ -0,0 +1,1961 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.MultipartConfigElement;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRegistration;
+import javax.servlet.SessionTrackingMode;
+import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.descriptor.JspPropertyGroupDescriptor;
+import javax.servlet.descriptor.TaglibDescriptor;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.FilterMapping;
+import org.eclipse.jetty.servlet.Holder;
+import org.eclipse.jetty.servlet.JspPropertyGroupServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler.JspConfig;
+import org.eclipse.jetty.servlet.ServletContextHandler.JspPropertyGroup;
+import org.eclipse.jetty.servlet.ServletContextHandler.TagLib;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlet.ServletMapping;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.security.Constraint;
+import org.eclipse.jetty.xml.XmlParser;
+
+/**
+ * StandardDescriptorProcessor
+ *
+ * Process a web.xml, web-defaults.xml, web-overrides.xml, web-fragment.xml.
+ */
+public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
+{
+    private static final Logger LOG = Log.getLogger(StandardDescriptorProcessor.class);
+
+    public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor";
+    
+    
+    
+    public StandardDescriptorProcessor ()
+    {
+ 
+        try
+        {
+            registerVisitor("context-param", this.getClass().getDeclaredMethod("visitContextParam", __signature));
+            registerVisitor("display-name", this.getClass().getDeclaredMethod("visitDisplayName", __signature));
+            registerVisitor("servlet", this.getClass().getDeclaredMethod("visitServlet",  __signature));
+            registerVisitor("servlet-mapping", this.getClass().getDeclaredMethod("visitServletMapping",  __signature));
+            registerVisitor("session-config", this.getClass().getDeclaredMethod("visitSessionConfig",  __signature));
+            registerVisitor("mime-mapping", this.getClass().getDeclaredMethod("visitMimeMapping",  __signature)); 
+            registerVisitor("welcome-file-list", this.getClass().getDeclaredMethod("visitWelcomeFileList",  __signature));
+            registerVisitor("locale-encoding-mapping-list", this.getClass().getDeclaredMethod("visitLocaleEncodingList",  __signature));
+            registerVisitor("error-page", this.getClass().getDeclaredMethod("visitErrorPage",  __signature));
+            registerVisitor("taglib", this.getClass().getDeclaredMethod("visitTagLib",  __signature));
+            registerVisitor("jsp-config", this.getClass().getDeclaredMethod("visitJspConfig",  __signature));
+            registerVisitor("security-constraint", this.getClass().getDeclaredMethod("visitSecurityConstraint",  __signature));
+            registerVisitor("login-config", this.getClass().getDeclaredMethod("visitLoginConfig",  __signature));
+            registerVisitor("security-role", this.getClass().getDeclaredMethod("visitSecurityRole",  __signature));
+            registerVisitor("filter", this.getClass().getDeclaredMethod("visitFilter",  __signature));
+            registerVisitor("filter-mapping", this.getClass().getDeclaredMethod("visitFilterMapping",  __signature));
+            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener",  __signature));
+            registerVisitor("distributable", this.getClass().getDeclaredMethod("visitDistributable",  __signature));
+        }
+        catch (Exception e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    public void start(WebAppContext context, Descriptor descriptor)
+    { 
+    }
+    
+    
+    
+    /** 
+     * {@inheritDoc}
+     */
+    public void end(WebAppContext context, Descriptor descriptor)
+    {
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    public void visitContextParam (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String name = node.getString("param-name", false, true);
+        String value = node.getString("param-value", false, true);
+        Origin o = context.getMetaData().getOrigin("context-param."+name);
+        switch (o)
+        {
+            case NotSet:
+            {
+                //just set it
+                context.getInitParams().put(name, value);
+                context.getMetaData().setOrigin("context-param."+name, descriptor);
+                break;
+            }
+            case WebXml:
+            case WebDefaults:
+            case WebOverride:
+            {
+                //previously set by a web xml, allow other web xml files to override
+                if (!(descriptor instanceof FragmentDescriptor))
+                {
+                    context.getInitParams().put(name, value);
+                    context.getMetaData().setOrigin("context-param."+name, descriptor); 
+                }
+                break;
+            }
+            case WebFragment:
+            {
+                //previously set by a web-fragment, this fragment's value must be the same
+                if (descriptor instanceof FragmentDescriptor)
+                {
+                    if (!((String)context.getInitParams().get(name)).equals(value))
+                        throw new IllegalStateException("Conflicting context-param "+name+"="+value+" in "+descriptor.getResource());
+                }
+                break;
+            }
+        }
+        if (LOG.isDebugEnabled()) 
+            LOG.debug("ContextParam: " + name + "=" + value);
+
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitDisplayName(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        //Servlet Spec 3.0 p. 74 Ignore from web-fragments
+        if (!(descriptor instanceof FragmentDescriptor))
+        {
+            context.setDisplayName(node.toString(false, true));
+            context.getMetaData().setOrigin("display-name", descriptor);
+        }
+    }
+    
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitServlet(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String id = node.getAttribute("id");
+
+        // initialize holder
+        String servlet_name = node.getString("servlet-name", false, true);
+        ServletHolder holder = context.getServletHandler().getServlet(servlet_name);
+          
+        /*
+         * If servlet of that name does not already exist, create it.
+         */
+        if (holder == null)
+        {
+            holder = context.getServletHandler().newServletHolder(Holder.Source.DESCRIPTOR);
+            holder.setName(servlet_name);
+            context.getServletHandler().addServlet(holder);
+        }
+
+        // init params  
+        Iterator<?> iParamsIter = node.iterator("init-param");
+        while (iParamsIter.hasNext())
+        {
+            XmlParser.Node paramNode = (XmlParser.Node) iParamsIter.next();
+            String pname = paramNode.getString("param-name", false, true);
+            String pvalue = paramNode.getString("param-value", false, true);
+            
+            Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.init-param."+pname);
+            
+            switch (origin)
+            {
+                case NotSet:
+                {
+                    //init-param not already set, so set it
+                    
+                    holder.setInitParameter(pname, pvalue); 
+                    context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
+                    //otherwise just ignore it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setInitParameter(pname, pvalue); 
+                        context.getMetaData().setOrigin(servlet_name+".servlet.init-param."+pname, descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //previously set by a web-fragment, make sure that the value matches, otherwise its an error
+                    if (!holder.getInitParameter(pname).equals(pvalue))
+                        throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
+                    break;
+                }
+            }  
+        }
+
+        String servlet_class = node.getString("servlet-class", false, true);
+
+        // Handle JSP
+        String jspServletClass=null;;
+
+        //Handle the default jsp servlet instance
+        if (id != null && id.equals("jsp"))
+        {
+            jspServletClass = servlet_class;
+            try
+            {
+                Loader.loadClass(this.getClass(), servlet_class);
+                
+                //Ensure there is a scratch dir
+                if (holder.getInitParameter("scratchdir") == null)
+                {
+                    File tmp = context.getTempDirectory();
+                    File scratch = new File(tmp, "jsp");
+                    if (!scratch.exists()) scratch.mkdir();
+                    holder.setInitParameter("scratchdir", scratch.getAbsolutePath());
+                }
+            }
+            catch (ClassNotFoundException e)
+            {
+                LOG.info("NO JSP Support for {}, did not find {}", context.getContextPath(), servlet_class);
+                jspServletClass = servlet_class = "org.eclipse.jetty.servlet.NoJspServlet";
+            }
+        }
+        
+       
+        //Set the servlet-class
+        if (servlet_class != null) 
+        {
+            ((WebDescriptor)descriptor).addClassName(servlet_class);
+            
+            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.servlet-class");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //the class of the servlet has not previously been set, so set it
+                    holder.setClassName(servlet_class);
+                    context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //the class of the servlet was set by a web xml file, only allow web-override/web-default to change it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setClassName(servlet_class);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.servlet-class", descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //the class was set by another fragment, ensure this fragment's value is the same
+                    if (!servlet_class.equals(holder.getClassName()))
+                        throw new IllegalStateException("Conflicting servlet-class "+servlet_class+" in "+descriptor.getResource());
+                    break;
+                }
+            }          
+        }
+
+        // Handle JSP file
+        String jsp_file = node.getString("jsp-file", false, true);
+        if (jsp_file != null)
+        {
+            holder.setForcedPath(jsp_file);
+            ServletHolder jsp=context.getServletHandler().getServlet("jsp");
+            if (jsp!=null)
+                holder.setClassName(jsp.getClassName());
+        }
+
+        // handle load-on-startup 
+        XmlParser.Node startup = node.get("load-on-startup");
+        if (startup != null)
+        {
+            String s = startup.toString(false, true).toLowerCase(Locale.ENGLISH);
+            int order = 0;
+            if (s.startsWith("t"))
+            {
+                LOG.warn("Deprecated boolean load-on-startup.  Please use integer");
+                order = 1; 
+            }
+            else
+            {
+                try
+                {
+                    if (s != null && s.trim().length() > 0) order = Integer.parseInt(s);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Cannot parse load-on-startup " + s + ". Please use integer");
+                    LOG.ignore(e);
+                }
+            }
+
+            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.load-on-startup");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //not already set, so set it now
+                    holder.setInitOrder(order);
+                    context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setInitOrder(order);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.load-on-startup", descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //it was already set by another fragment, if we're parsing a fragment, the values must match
+                    if (order != holder.getInitOrder())
+                        throw new IllegalStateException("Conflicting load-on-startup value in "+descriptor.getResource());
+                    break;
+                }
+            } 
+        }
+
+        Iterator sRefsIter = node.iterator("security-role-ref");
+        while (sRefsIter.hasNext())
+        {
+            XmlParser.Node securityRef = (XmlParser.Node) sRefsIter.next();
+            String roleName = securityRef.getString("role-name", false, true);
+            String roleLink = securityRef.getString("role-link", false, true);
+            if (roleName != null && roleName.length() > 0 && roleLink != null && roleLink.length() > 0)
+            {
+                if (LOG.isDebugEnabled()) LOG.debug("link role " + roleName + " to " + roleLink + " for " + this);
+                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.role-name."+roleName);
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //set it
+                        holder.setUserRoleLink(roleName, roleLink);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //only another web xml descriptor (web-default,web-override web.xml) can override an already set value
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            holder.setUserRoleLink(roleName, roleLink);
+                            context.getMetaData().setOrigin(servlet_name+".servlet.role-name."+roleName, descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        if (!holder.getUserRoleLink(roleName).equals(roleLink))
+                            throw new IllegalStateException("Conflicting role-link for role-name "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                LOG.warn("Ignored invalid security-role-ref element: " + "servlet-name=" + holder.getName() + ", " + securityRef);
+            }
+        }
+
+        
+        XmlParser.Node run_as = node.get("run-as");
+        if (run_as != null)
+        { 
+            String roleName = run_as.getString("role-name", false, true);
+
+            if (roleName != null)
+            {
+                Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.run-as");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //run-as not set, so set it
+                        holder.setRunAsRole(roleName);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //run-as was set by a web xml, only allow it to be changed if we're currently parsing another web xml(override/default)
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            holder.setRunAsRole(roleName);
+                            context.getMetaData().setOrigin(servlet_name+".servlet.run-as", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //run-as was set by another fragment, this fragment must show the same value
+                        if (!holder.getRunAsRole().equals(roleName))
+                            throw new IllegalStateException("Conflicting run-as role "+roleName+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                        break;
+                    }    
+                }
+            }
+        }
+
+        String async=node.getString("async-supported",false,true);
+        if (async!=null)
+        {
+            boolean val = async.length()==0||Boolean.valueOf(async);
+            Origin o =context.getMetaData().getOrigin(servlet_name+".servlet.async-supported");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //set it
+                    holder.setAsyncSupported(val);
+                    context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setAsyncSupported(val);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.async-supported", descriptor);  
+                    }             
+                    break;
+                }
+                case WebFragment:
+                {
+                    //async-supported set by another fragment, this fragment's value must match
+                    if (holder.isAsyncSupported() != val)
+                        throw new IllegalStateException("Conflicting async-supported="+async+" for servlet "+servlet_name+" in "+descriptor.getResource());
+                    break;
+                }
+            }
+        }
+
+        String enabled = node.getString("enabled", false, true);
+        if (enabled!=null)
+        {
+            boolean is_enabled = enabled.length()==0||Boolean.valueOf(enabled);     
+            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.enabled");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //hasn't been set yet, so set it                
+                    holder.setEnabled(is_enabled);
+                    context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //was set in a web xml descriptor, only allow override from another web xml descriptor
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setEnabled(is_enabled);   
+                        context.getMetaData().setOrigin(servlet_name+".servlet.enabled", descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //was set by another fragment, this fragment's value must match
+                    if (holder.isEnabled() != is_enabled)
+                        throw new IllegalStateException("Conflicting value of servlet enabled for servlet "+servlet_name+" in "+descriptor.getResource());
+                    break;
+                }
+            }
+        }
+        
+        /*
+         * If multipart config not set, then set it and record it was by the web.xml or fragment.
+         * If it was set by web.xml then if this is a fragment, ignore the settings.
+         * If it was set by a fragment, if this is a fragment and the values are different, error!
+         */
+        XmlParser.Node multipart = node.get("multipart-config");
+        if (multipart != null)
+        {
+            String location = multipart.getString("location", false, true);
+            String maxFile = multipart.getString("max-file-size", false, true);
+            String maxRequest = multipart.getString("max-request-size", false, true);
+            String threshold = multipart.getString("file-size-threshold",false,true);
+            MultipartConfigElement element = new MultipartConfigElement(location,
+                                                                        (maxFile==null||"".equals(maxFile)?-1L:Long.parseLong(maxFile)),
+                                                                        (maxRequest==null||"".equals(maxRequest)?-1L:Long.parseLong(maxRequest)),
+                                                                        (threshold==null||"".equals(threshold)?0:Integer.parseInt(threshold)));
+            
+            Origin o = context.getMetaData().getOrigin(servlet_name+".servlet.multipart-config");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //hasn't been set, so set it
+                    holder.getRegistration().setMultipartConfig(element);
+                    context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //was set in a web xml, only allow changes if we're parsing another web xml (web.xml/web-default.xml/web-override.xml)
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.getRegistration().setMultipartConfig(element);
+                        context.getMetaData().setOrigin(servlet_name+".servlet.multipart-config", descriptor);  
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //another fragment set the value, this fragment's values must match exactly or it is an error
+                    MultipartConfigElement cfg = ((ServletHolder.Registration)holder.getRegistration()).getMultipartConfig();
+                    
+                    if (cfg.getMaxFileSize() != element.getMaxFileSize())
+                        throw new IllegalStateException("Conflicting multipart-config max-file-size for servlet "+servlet_name+" in "+descriptor.getResource());
+                    if (cfg.getMaxRequestSize() != element.getMaxRequestSize())
+                        throw new IllegalStateException("Conflicting multipart-config max-request-size for servlet "+servlet_name+" in "+descriptor.getResource());
+                    if (cfg.getFileSizeThreshold() != element.getFileSizeThreshold())
+                        throw new IllegalStateException("Conflicting multipart-config file-size-threshold for servlet "+servlet_name+" in "+descriptor.getResource());
+                    if ((cfg.getLocation() != null && (element.getLocation() == null || element.getLocation().length()==0))
+                            || (cfg.getLocation() == null && (element.getLocation()!=null || element.getLocation().length() > 0)))
+                        throw new IllegalStateException("Conflicting multipart-config location for servlet "+servlet_name+" in "+descriptor.getResource());
+                    break;
+                }
+            } 
+        }
+    }
+    
+    
+
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitServletMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        //Servlet Spec 3.0, p74
+        //servlet-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
+        //Maintenance update 3.0a to spec:
+        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
+        //  <servlet-mapping> declared in web.xml overrides the mapping for the servlet specified in the web-fragment.xml
+
+        String servlet_name = node.getString("servlet-name", false, true); 
+        Origin origin = context.getMetaData().getOrigin(servlet_name+".servlet.mappings");
+        
+        switch (origin)
+        {
+            case NotSet:
+            {
+                //no servlet mappings
+                context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
+                ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
+                mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
+                break;
+            }
+            case WebXml:
+            case WebDefaults:
+            case WebOverride:
+            {
+                //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
+                //otherwise just ignore it
+                if (!(descriptor instanceof FragmentDescriptor))
+                {
+                   addServletMapping(servlet_name, node, context, descriptor);
+                }
+                break;
+            }
+            case WebFragment:
+            {
+                //mappings previously set by another web-fragment, so merge in this web-fragment's mappings
+                addServletMapping(servlet_name, node, context, descriptor);
+                break;
+            }
+        }        
+    }
+    
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitSessionConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        XmlParser.Node tNode = node.get("session-timeout");
+        if (tNode != null)
+        {
+            int timeout = Integer.parseInt(tNode.toString(false, true));
+            context.getSessionHandler().getSessionManager().setMaxInactiveInterval(timeout * 60);
+        }
+        
+        //Servlet Spec 3.0 
+        // <tracking-mode>
+        // this is additive across web-fragments
+        Iterator iter = node.iterator("tracking-mode");
+        if (iter.hasNext())
+        { 
+            Set<SessionTrackingMode> modes = null;
+            Origin o = context.getMetaData().getOrigin("session.tracking-mode");
+            switch (o)
+            {
+                case NotSet://not previously set, starting fresh
+                case WebDefaults://previously set in web defaults, allow this descriptor to start fresh
+                {
+                    
+                    modes = new HashSet<SessionTrackingMode>();
+                    context.getMetaData().setOrigin("session.tracking-mode", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebFragment:
+                case WebOverride:
+                {
+                    //if setting from an override descriptor, start afresh, otherwise add-in tracking-modes
+                    if (descriptor instanceof OverrideDescriptor)
+                        modes = new HashSet<SessionTrackingMode>();
+                    else
+                        modes = new HashSet<SessionTrackingMode>(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes());
+                    context.getMetaData().setOrigin("session.tracking-mode", descriptor);
+                    break;
+                }       
+            }
+            
+            while (iter.hasNext())
+            {
+                XmlParser.Node mNode = (XmlParser.Node) iter.next();
+                String trackMode = mNode.toString(false, true);
+                modes.add(SessionTrackingMode.valueOf(trackMode));
+            }
+            context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes);   
+        }
+       
+        
+        //Servlet Spec 3.0 
+        //<cookie-config>
+        XmlParser.Node cookieConfig = node.get("cookie-config");
+        if (cookieConfig != null)
+        {
+            //  <name>
+            String name = cookieConfig.getString("name", false, true);
+            if (name != null)
+            {
+                Origin o = context.getMetaData().getOrigin("cookie-config.name");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><name> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
+                        context.getMetaData().setOrigin("cookie-config.name", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><name> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setName(name);
+                            context.getMetaData().setOrigin("cookie-config.name", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getName().equals(name))                  
+                            throw new IllegalStateException("Conflicting cookie-config name "+name+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <domain>
+            String domain = cookieConfig.getString("domain", false, true);
+            if (domain != null)
+            {
+                Origin o = context.getMetaData().getOrigin("cookie-config.domain");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><domain> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
+                        context.getMetaData().setOrigin("cookie-config.domain", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setDomain(domain);
+                            context.getMetaData().setOrigin("cookie-config.domain", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getDomain().equals(domain))                  
+                            throw new IllegalStateException("Conflicting cookie-config domain "+domain+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <path>
+            String path = cookieConfig.getString("path", false, true);
+            if (path != null)
+            {
+                Origin o = context.getMetaData().getOrigin("cookie-config.path");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><domain> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
+                        context.getMetaData().setOrigin("cookie-config.path", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><domain> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setPath(path);
+                            context.getMetaData().setOrigin("cookie-config.path", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getPath().equals(path))                  
+                            throw new IllegalStateException("Conflicting cookie-config path "+path+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <comment>
+            String comment = cookieConfig.getString("comment", false, true);
+            if (comment != null)
+            {
+                Origin o = context.getMetaData().getOrigin("cookie-config.comment");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><comment> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
+                        context.getMetaData().setOrigin("cookie-config.comment", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><comment> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setComment(comment);
+                            context.getMetaData().setOrigin("cookie-config.comment", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (!context.getSessionHandler().getSessionManager().getSessionCookieConfig().getComment().equals(comment))                  
+                            throw new IllegalStateException("Conflicting cookie-config comment "+comment+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <http-only>true/false
+            tNode = cookieConfig.get("http-only");
+            if (tNode != null)
+            {
+                boolean httpOnly = Boolean.parseBoolean(tNode.toString(false,true));           
+                Origin o = context.getMetaData().getOrigin("cookie-config.http-only");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><http-only> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
+                        context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><http-only> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setHttpOnly(httpOnly);
+                            context.getMetaData().setOrigin("cookie-config.http-only", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isHttpOnly() != httpOnly)       
+                            throw new IllegalStateException("Conflicting cookie-config http-only "+httpOnly+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <secure>true/false
+            tNode = cookieConfig.get("secure");
+            if (tNode != null)
+            {
+                boolean secure = Boolean.parseBoolean(tNode.toString(false,true));
+                Origin o = context.getMetaData().getOrigin("cookie-config.secure");
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no <cookie-config><secure> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
+                        context.getMetaData().setOrigin("cookie-config.secure", descriptor);
+                        break;
+                    }                   
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //<cookie-config><secure> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setSecure(secure);
+                            context.getMetaData().setOrigin("cookie-config.secure", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().isSecure() != secure)       
+                            throw new IllegalStateException("Conflicting cookie-config secure "+secure+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+            
+            //  <max-age>
+            tNode = cookieConfig.get("max-age");
+            if (tNode != null)
+            {
+                int maxAge = Integer.parseInt(tNode.toString(false,true));
+                Origin o = context.getMetaData().getOrigin("cookie-config.max-age");
+                switch (o)
+                {
+                    case NotSet:
+                    { 
+                        //no <cookie-config><max-age> set yet, accept it
+                        context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
+                        context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {   
+                        //<cookie-config><max-age> set in a web xml, only allow web-default/web-override to change
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.getSessionHandler().getSessionManager().getSessionCookieConfig().setMaxAge(maxAge);
+                            context.getMetaData().setOrigin("cookie-config.max-age", descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a web-fragment set the value, all web-fragments must have the same value
+                        if (context.getSessionHandler().getSessionManager().getSessionCookieConfig().getMaxAge() != maxAge)
+                            throw new IllegalStateException("Conflicting cookie-config max-age "+maxAge+" in "+descriptor.getResource());
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitMimeMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String extension = node.getString("extension", false, true);
+        if (extension != null && extension.startsWith(".")) 
+            extension = extension.substring(1);
+        String mimeType = node.getString("mime-type", false, true);
+        if (extension != null)
+        {
+            Origin o = context.getMetaData().getOrigin("extension."+extension);
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //no mime-type set for the extension yet
+                    context.getMimeTypes().addMimeMapping(extension, mimeType);
+                    context.getMetaData().setOrigin("extension."+extension, descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //a mime-type was set for the extension in a web xml, only allow web-default/web-override to change
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        context.getMimeTypes().addMimeMapping(extension, mimeType);
+                        context.getMetaData().setOrigin("extension."+extension, descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //a web-fragment set the value, all web-fragments must have the same value
+                    if (!context.getMimeTypes().getMimeByExtension("."+extension).equals(context.getMimeTypes().CACHE.lookup(mimeType)))
+                        throw new IllegalStateException("Conflicting mime-type "+mimeType+" for extension "+extension+" in "+descriptor.getResource());
+                    break;
+                }
+            }
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitWelcomeFileList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        Origin o = context.getMetaData().getOrigin("welcome-file-list");
+        switch (o)
+        {
+            case NotSet:
+            {
+                context.getMetaData().setOrigin("welcome-file-list", descriptor);
+                addWelcomeFiles(context,node);
+                break;
+            }
+            case WebXml:
+            {
+                //web.xml set the welcome-file-list, all other descriptors then just merge in
+                addWelcomeFiles(context,node);
+                break;
+            }
+            case WebDefaults:
+            {
+                //if web-defaults set the welcome-file-list first and
+                //we're processing web.xml then reset the welcome-file-list
+                if (!(descriptor instanceof DefaultsDescriptor) && !(descriptor instanceof OverrideDescriptor) && !(descriptor instanceof FragmentDescriptor))
+                {
+                    context.setWelcomeFiles(new String[0]);
+                }
+                addWelcomeFiles(context,node);
+                break;
+            }
+            case WebOverride:
+            {
+                //web-override set the list, all other descriptors just merge in
+                addWelcomeFiles(context,node);
+                break;
+            }
+            case WebFragment:
+            {
+                //A web-fragment first set the welcome-file-list. Other descriptors just add. 
+                addWelcomeFiles(context,node);
+                break;
+            }
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitLocaleEncodingList(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        Iterator<XmlParser.Node> iter = node.iterator("locale-encoding-mapping");
+        while (iter.hasNext())
+        {
+            XmlParser.Node mapping = iter.next();
+            String locale = mapping.getString("locale", false, true);
+            String encoding = mapping.getString("encoding", false, true);
+            
+            if (encoding != null)
+            {
+                Origin o = context.getMetaData().getOrigin("locale-encoding."+locale);
+                switch (o)
+                {
+                    case NotSet:
+                    {
+                        //no mapping for the locale yet, so set it
+                        context.addLocaleEncoding(locale, encoding);
+                        context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    {
+                        //a value was set in a web descriptor, only allow another web descriptor to change it (web-default/web-override)
+                        if (!(descriptor instanceof FragmentDescriptor))
+                        {
+                            context.addLocaleEncoding(locale, encoding);
+                            context.getMetaData().setOrigin("locale-encoding."+locale, descriptor);
+                        }
+                        break;
+                    }
+                    case WebFragment:
+                    {
+                        //a value was set by a web-fragment, all fragments must have the same value
+                        if (!encoding.equals(context.getLocaleEncoding(locale)))
+                            throw new IllegalStateException("Conflicting loacle-encoding mapping for locale "+locale+" in "+descriptor.getResource());
+                        break;                    
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitErrorPage(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String error = node.getString("error-code", false, true);
+        int code=0;
+        if (error == null || error.length() == 0) 
+        {
+            error = node.getString("exception-type", false, true);
+            if (error == null || error.length() == 0)
+                error = ErrorPageErrorHandler.GLOBAL_ERROR_PAGE;
+        }
+        else
+            code=Integer.valueOf(error);
+        
+        String location = node.getString("location", false, true);
+        ErrorPageErrorHandler handler = (ErrorPageErrorHandler)context.getErrorHandler();
+        Origin o = context.getMetaData().getOrigin("error."+error);
+        
+        switch (o)
+        {
+            case NotSet:
+            {
+                //no error page setup for this code or exception yet
+                if (code>0)
+                    handler.addErrorPage(code,location);
+                else
+                    handler.addErrorPage(error,location);
+                context.getMetaData().setOrigin("error."+error, descriptor);
+                break;
+            }
+            case WebXml:
+            case WebDefaults:
+            case WebOverride:
+            {
+                //an error page setup was set in web.xml, only allow other web xml descriptors to override it
+                if (!(descriptor instanceof FragmentDescriptor))
+                {
+                    if (descriptor instanceof OverrideDescriptor || descriptor instanceof DefaultsDescriptor)
+                    {
+                        if (code>0)
+                            handler.addErrorPage(code,location);
+                        else
+                            handler.addErrorPage(error,location);
+                        context.getMetaData().setOrigin("error."+error, descriptor);
+                    }
+                    else
+                        throw new IllegalStateException("Duplicate global error-page "+location);
+                }
+                break;
+            }
+            case WebFragment:
+            {
+                //another web fragment set the same error code or exception, if its different its an error
+                if (!handler.getErrorPages().get(error).equals(location))
+                    throw new IllegalStateException("Conflicting error-code or exception-type "+error+" in "+descriptor.getResource());
+                break;
+            }
+        }
+       
+    }
+    
+    /**
+     * @param context
+     * @param node
+     */
+    protected void addWelcomeFiles(WebAppContext context, XmlParser.Node node)
+    {
+        Iterator<XmlParser.Node> iter = node.iterator("welcome-file");
+        while (iter.hasNext())
+        {
+            XmlParser.Node indexNode = (XmlParser.Node) iter.next();
+            String welcome = indexNode.toString(false, true);
+            
+            //Servlet Spec 3.0 p. 74 welcome files are additive
+            if (welcome != null && welcome.trim().length() > 0)
+                context.setWelcomeFiles((String[])LazyList.addToArray(context.getWelcomeFiles(),welcome,String.class));
+        }
+    }
+    
+    
+    /**
+     * @param servletName
+     * @param node
+     * @param context
+     */
+    protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
+    {
+        ServletMapping mapping = new ServletMapping();
+        mapping.setServletName(servletName);
+        
+        List<String> paths = new ArrayList<String>();
+        Iterator<XmlParser.Node> iter = node.iterator("url-pattern");
+        while (iter.hasNext())
+        {
+            String p = iter.next().toString(false, true);
+            p = normalizePattern(p);
+            paths.add(p);
+            context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
+        }
+        mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
+        context.getServletHandler().addServletMapping(mapping);
+        return mapping;
+    }
+    
+    /**
+     * @param filterName
+     * @param node
+     * @param context
+     */
+    protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
+    {
+        FilterMapping mapping = new FilterMapping();
+        mapping.setFilterName(filterName);
+
+        List<String> paths = new ArrayList<String>();
+        Iterator<XmlParser.Node>  iter = node.iterator("url-pattern");
+        while (iter.hasNext())
+        {
+            String p = iter.next().toString(false, true);
+            p = normalizePattern(p);
+            paths.add(p);
+            context.getMetaData().setOrigin(filterName+".filter.mapping."+p, descriptor);
+        }
+        mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
+
+        List<String> names = new ArrayList<String>();
+        iter = node.iterator("servlet-name");
+        while (iter.hasNext())
+        {
+            String n = ((XmlParser.Node) iter.next()).toString(false, true);
+            names.add(n);
+        }
+        mapping.setServletNames((String[]) names.toArray(new String[names.size()]));
+
+        
+        List<DispatcherType> dispatches = new ArrayList<DispatcherType>();
+        iter=node.iterator("dispatcher");
+        while(iter.hasNext())
+        {
+            String d=((XmlParser.Node)iter.next()).toString(false,true);
+            dispatches.add(FilterMapping.dispatch(d));
+        }
+        
+        if (dispatches.size()>0)
+            mapping.setDispatcherTypes(EnumSet.copyOf(dispatches));
+
+        context.getServletHandler().addFilterMapping(mapping);
+    }
+    
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitTagLib(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        //Additive across web.xml and web-fragment.xml
+        String uri = node.getString("taglib-uri", false, true);
+        String location = node.getString("taglib-location", false, true);
+
+        context.setResourceAlias(uri, location);
+        
+        JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
+        if (config == null)
+        {
+            config = new JspConfig();
+            context.getServletContext().setJspConfigDescriptor(config);
+        }
+        
+        TagLib tl = new TagLib();
+        tl.setTaglibLocation(location);
+        tl.setTaglibURI(uri);
+        config.addTaglibDescriptor(tl);
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitJspConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {   
+        //Additive across web.xml and web-fragment.xml
+        JspConfig config = (JspConfig)context.getServletContext().getJspConfigDescriptor();
+        if (config == null)
+        {
+           config = new JspConfig();
+           context.getServletContext().setJspConfigDescriptor(config);
+        }
+        
+        
+        for (int i = 0; i < node.size(); i++)
+        {
+            Object o = node.get(i);
+            if (o instanceof XmlParser.Node && "taglib".equals(((XmlParser.Node) o).getTag())) 
+                visitTagLib(context,descriptor, (XmlParser.Node) o);
+        }
+
+        // Map URLs from jsp property groups to JSP servlet.
+        // this is more JSP stupidness creeping into the servlet spec
+        Iterator<XmlParser.Node> iter = node.iterator("jsp-property-group");
+        List<String> paths = new ArrayList<String>();
+        while (iter.hasNext())
+        {
+            JspPropertyGroup jpg = new JspPropertyGroup();
+            config.addJspPropertyGroup(jpg);
+            XmlParser.Node group = iter.next();
+            
+            //url-patterns
+            Iterator<XmlParser.Node> iter2 = group.iterator("url-pattern");
+            while (iter2.hasNext())
+            {
+                String url = iter2.next().toString(false, true);
+                url = normalizePattern(url);
+                paths.add( url);
+                jpg.addUrlPattern(url);
+            }
+            
+            jpg.setElIgnored(group.getString("el-ignored", false, true));
+            jpg.setPageEncoding(group.getString("page-encoding", false, true));
+            jpg.setScriptingInvalid(group.getString("scripting-invalid", false, true));
+            jpg.setIsXml(group.getString("is-xml", false, true));
+            jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true));
+            jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true));
+            jpg.setDefaultContentType(group.getString("default-content-type", false, true));
+            jpg.setBuffer(group.getString("buffer", false, true));
+            jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
+            
+            //preludes
+            Iterator<XmlParser.Node> preludes = group.iterator("include-prelude");
+            while (preludes.hasNext())
+            {
+                String prelude = preludes.next().toString(false, true);
+                jpg.addIncludePrelude(prelude);
+            }
+            //codas
+            Iterator<XmlParser.Node> codas = group.iterator("include-coda");
+            while (codas.hasNext())
+            {
+                String coda = codas.next().toString(false, true);
+                jpg.addIncludeCoda(coda);
+            }
+            
+            if (LOG.isDebugEnabled()) LOG.debug(config.toString());
+        }
+
+        if (paths.size() > 0)
+        {
+            ServletHandler handler = context.getServletHandler();
+            ServletHolder jsp_pg_servlet = handler.getServlet(JspPropertyGroupServlet.NAME);
+            if (jsp_pg_servlet==null)
+            {
+                jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler));
+                handler.addServlet(jsp_pg_servlet);
+            }
+
+            ServletMapping mapping = new ServletMapping();
+            mapping.setServletName(JspPropertyGroupServlet.NAME);
+            mapping.setPathSpecs(paths.toArray(new String[paths.size()]));
+            context.getServletHandler().addServletMapping(mapping);
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitSecurityConstraint(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        Constraint scBase = new Constraint();
+
+        //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive 
+        //across fragments
+        
+        //TODO: need to remember origin of the constraints
+        try
+        {
+            XmlParser.Node auths = node.get("auth-constraint");
+
+            if (auths != null)
+            {
+                scBase.setAuthenticate(true);
+                // auth-constraint
+                Iterator<XmlParser.Node> iter = auths.iterator("role-name");
+                List<String> roles = new ArrayList<String>();
+                while (iter.hasNext())
+                {
+                    String role = iter.next().toString(false, true);
+                    roles.add(role);
+                }
+                scBase.setRoles(roles.toArray(new String[roles.size()]));
+            }
+
+            XmlParser.Node data = node.get("user-data-constraint");
+            if (data != null)
+            {
+                data = data.get("transport-guarantee");
+                String guarantee = data.toString(false, true).toUpperCase(Locale.ENGLISH);
+                if (guarantee == null || guarantee.length() == 0 || "NONE".equals(guarantee))
+                    scBase.setDataConstraint(Constraint.DC_NONE);
+                else if ("INTEGRAL".equals(guarantee))
+                    scBase.setDataConstraint(Constraint.DC_INTEGRAL);
+                else if ("CONFIDENTIAL".equals(guarantee))
+                    scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
+                else
+                {
+                    LOG.warn("Unknown user-data-constraint:" + guarantee);
+                    scBase.setDataConstraint(Constraint.DC_CONFIDENTIAL);
+                }
+            }
+            Iterator<XmlParser.Node> iter = node.iterator("web-resource-collection");
+            while (iter.hasNext())
+            {
+                XmlParser.Node collection =  iter.next();
+                String name = collection.getString("web-resource-name", false, true);
+                Constraint sc = (Constraint) scBase.clone();
+                sc.setName(name);
+
+                Iterator<XmlParser.Node> iter2 = collection.iterator("url-pattern");
+                while (iter2.hasNext())
+                {
+                    String url = iter2.next().toString(false, true);
+                    url = normalizePattern(url);
+                    //remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
+                    context.getMetaData().setOrigin("constraint.url."+url, descriptor);
+                    
+                    Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
+                    Iterator<XmlParser.Node> iter4 = collection.iterator("http-method-omission");
+                   
+                    if (iter3.hasNext())
+                    {
+                        if (iter4.hasNext())
+                            throw new IllegalStateException ("web-resource-collection cannot contain both http-method and http-method-omission");
+                        
+                        //configure all the http-method elements for each url
+                        while (iter3.hasNext())
+                        {
+                            String method = ((XmlParser.Node) iter3.next()).toString(false, true);
+                            ConstraintMapping mapping = new ConstraintMapping();
+                            mapping.setMethod(method);
+                            mapping.setPathSpec(url);
+                            mapping.setConstraint(sc);                                                      
+                            ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
+                        }
+                    }
+                    else if (iter4.hasNext())
+                    {
+                        //configure all the http-method-omission elements for each url
+                        while (iter4.hasNext())
+                        {
+                            String method = ((XmlParser.Node)iter4.next()).toString(false, true);
+                            ConstraintMapping mapping = new ConstraintMapping();
+                            mapping.setMethodOmissions(new String[]{method});
+                            mapping.setPathSpec(url);
+                            mapping.setConstraint(sc);
+                            ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
+                        }
+                    }
+                    else
+                    {
+                        //No http-methods or http-method-omissions specified, the constraint applies to all
+                        ConstraintMapping mapping = new ConstraintMapping();
+                        mapping.setPathSpec(url);
+                        mapping.setConstraint(sc);
+                        ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
+                    }
+                } 
+            }
+        }
+        catch (CloneNotSupportedException e)
+        {
+            LOG.warn(e);
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     * @throws Exception
+     */
+    protected void visitLoginConfig(WebAppContext context, Descriptor descriptor, XmlParser.Node node) throws Exception
+    {
+        //ServletSpec 3.0 p74 says elements present 0/1 time if specified in web.xml take
+        //precendece over any web-fragment. If not specified in web.xml, then if specified
+        //in a web-fragment must be the same across all web-fragments.
+        XmlParser.Node method = node.get("auth-method");
+        if (method != null)
+        {
+            //handle auth-method merge
+            Origin o = context.getMetaData().getOrigin("auth-method");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //not already set, so set it now
+                    context.getSecurityHandler().setAuthMethod(method.toString(false, true));
+                    context.getMetaData().setOrigin("auth-method", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //if it was already set by a web xml descriptor and we're parsing another web xml descriptor, then override it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        context.getSecurityHandler().setAuthMethod(method.toString(false, true));
+                        context.getMetaData().setOrigin("auth-method", descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //it was already set by another fragment, if we're parsing a fragment, the values must match
+                    if (!context.getSecurityHandler().getAuthMethod().equals(method.toString(false, true)))
+                        throw new IllegalStateException("Conflicting auth-method value in "+descriptor.getResource());
+                    break;
+                }
+            } 
+            
+            //handle realm-name merge
+            XmlParser.Node name = node.get("realm-name");
+            String nameStr = (name == null ? "default" : name.toString(false, true));
+            o = context.getMetaData().getOrigin("realm-name");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //no descriptor has set the realm-name yet, so set it
+                    context.getSecurityHandler().setRealmName(nameStr);
+                    context.getMetaData().setOrigin("realm-name", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //set by a web xml file (web.xml/web-default.xm/web-override.xml), only allow it to be changed by another web xml file
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        context.getSecurityHandler().setRealmName(nameStr);
+                        context.getMetaData().setOrigin("realm-name", descriptor); 
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //a fragment set it, and we must be parsing another fragment, so the values must match
+                    if (!context.getSecurityHandler().getRealmName().equals(nameStr))
+                        throw new IllegalStateException("Conflicting realm-name value in "+descriptor.getResource());
+                    break;
+                }
+            }
+ 
+            if (Constraint.__FORM_AUTH.equals(context.getSecurityHandler().getAuthMethod()))
+            {  
+                XmlParser.Node formConfig = node.get("form-login-config");
+                if (formConfig != null)
+                {
+                    String loginPageName = null;
+                    XmlParser.Node loginPage = formConfig.get("form-login-page");
+                    if (loginPage != null) 
+                        loginPageName = loginPage.toString(false, true);
+                    String errorPageName = null;
+                    XmlParser.Node errorPage = formConfig.get("form-error-page");
+                    if (errorPage != null) 
+                        errorPageName = errorPage.toString(false, true);
+                    
+                    //handle form-login-page
+                    o = context.getMetaData().getOrigin("form-login-page");
+                    switch (o)
+                    {
+                        case NotSet:
+                        {
+                            //Never been set before, so accept it
+                            context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
+                            context.getMetaData().setOrigin("form-login-page",descriptor);
+                            break;
+                        }
+                        case WebXml:
+                        case WebDefaults:
+                        case WebOverride:
+                        {
+                            //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
+                            if (!(descriptor instanceof FragmentDescriptor))
+                            {
+                                context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE,loginPageName);
+                                context.getMetaData().setOrigin("form-login-page",descriptor);
+                            }
+                            break;
+                        }
+                        case WebFragment:
+                        {
+                            //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
+                            if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_LOGIN_PAGE).equals(loginPageName))
+                                throw new IllegalStateException("Conflicting form-login-page value in "+descriptor.getResource());
+                            break;
+                        }
+                    }
+                    
+                    //handle form-error-page
+                    o = context.getMetaData().getOrigin("form-error-page");
+                    switch (o)
+                    {
+                        case NotSet:
+                        {
+                            //Never been set before, so accept it
+                            context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
+                            context.getMetaData().setOrigin("form-error-page",descriptor);
+                            break;
+                        }
+                        case WebXml:
+                        case WebDefaults:
+                        case WebOverride:
+                        {
+                            //a web xml descriptor previously set it, only allow another one to change it (web.xml/web-default.xml/web-override.xml)
+                            if (!(descriptor instanceof FragmentDescriptor))
+                            {
+                                context.getSecurityHandler().setInitParameter(FormAuthenticator.__FORM_ERROR_PAGE,errorPageName);
+                                context.getMetaData().setOrigin("form-error-page",descriptor);
+                            }
+                            break;
+                        }
+                        case WebFragment:
+                        {
+                            //a web-fragment previously set it. We must be parsing yet another web-fragment, so the values must agree
+                            if (!context.getSecurityHandler().getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE).equals(errorPageName))
+                                throw new IllegalStateException("Conflicting form-error-page value in "+descriptor.getResource());
+                            break;
+                        }
+                    }              
+                }
+                else
+                {
+                    throw new IllegalStateException("!form-login-config");
+                }
+            }
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitSecurityRole(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        //ServletSpec 3.0, p74 elements with multiplicity >1 are additive when merged
+        XmlParser.Node roleNode = node.get("role-name");
+        String role = roleNode.toString(false, true);
+        ((ConstraintAware)context.getSecurityHandler()).addRole(role);
+    }
+    
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String name = node.getString("filter-name", false, true);
+        FilterHolder holder = context.getServletHandler().getFilter(name);
+        if (holder == null)
+        {
+            holder = context.getServletHandler().newFilterHolder(Holder.Source.DESCRIPTOR);
+            holder.setName(name);
+            context.getServletHandler().addFilter(holder);
+        }
+
+        String filter_class = node.getString("filter-class", false, true);
+        if (filter_class != null) 
+        {
+            ((WebDescriptor)descriptor).addClassName(filter_class);
+            
+            Origin o = context.getMetaData().getOrigin(name+".filter.filter-class");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //no class set yet
+                    holder.setClassName(filter_class);
+                    context.getMetaData().setOrigin(name+".filter.filter-class", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //filter class was set in web.xml, only allow other web xml descriptors (override/default) to change it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setClassName(filter_class);
+                        context.getMetaData().setOrigin(name+".filter.filter-class", descriptor); 
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //the filter class was set up by a web fragment, all fragments must be the same
+                    if (!holder.getClassName().equals(filter_class))
+                        throw new IllegalStateException("Conflicting filter-class for filter "+name+" in "+descriptor.getResource());
+                    break;
+                }
+            }
+           
+        }
+
+        Iterator<XmlParser.Node>  iter = node.iterator("init-param");
+        while (iter.hasNext())
+        {
+            XmlParser.Node paramNode = iter.next();
+            String pname = paramNode.getString("param-name", false, true);
+            String pvalue = paramNode.getString("param-value", false, true);
+            
+            Origin origin = context.getMetaData().getOrigin(name+".filter.init-param."+pname);
+            switch (origin)
+            {
+                case NotSet:
+                {
+                    //init-param not already set, so set it
+                    holder.setInitParameter(pname, pvalue); 
+                    context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //previously set by a web xml descriptor, if we're parsing another web xml descriptor allow override
+                    //otherwise just ignore it
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setInitParameter(pname, pvalue); 
+                        context.getMetaData().setOrigin(name+".filter.init-param."+pname, descriptor);
+                    }
+                    break;
+                }
+                case WebFragment:
+                {
+                    //previously set by a web-fragment, make sure that the value matches, otherwise its an error
+                    if (!holder.getInitParameter(pname).equals(pvalue))
+                        throw new IllegalStateException("Mismatching init-param "+pname+"="+pvalue+" in "+descriptor.getResource());
+                    break;
+                }
+            }  
+        }
+
+        String async=node.getString("async-supported",false,true);
+        if (async!=null)
+            holder.setAsyncSupported(async.length()==0||Boolean.valueOf(async));
+        if (async!=null)
+        {
+            boolean val = async.length()==0||Boolean.valueOf(async);
+            Origin o = context.getMetaData().getOrigin(name+".filter.async-supported");
+            switch (o)
+            {
+                case NotSet:
+                {
+                    //set it
+                    holder.setAsyncSupported(val);
+                    context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);
+                    break;
+                }
+                case WebXml:
+                case WebDefaults:
+                case WebOverride:
+                {
+                    //async-supported set by previous web xml descriptor, only allow override if we're parsing another web descriptor(web.xml/web-override.xml/web-default.xml)
+                    if (!(descriptor instanceof FragmentDescriptor))
+                    {
+                        holder.setAsyncSupported(val);
+                        context.getMetaData().setOrigin(name+".filter.async-supported", descriptor);  
+                    }             
+                    break;
+                }
+                case WebFragment:
+                {
+                    //async-supported set by another fragment, this fragment's value must match
+                    if (holder.isAsyncSupported() != val)
+                        throw new IllegalStateException("Conflicting async-supported="+async+" for filter "+name+" in "+descriptor.getResource());
+                    break;
+                }
+            }
+        }
+        
+    }
+
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitFilterMapping(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        //Servlet Spec 3.0, p74
+        //filter-mappings are always additive, whether from web xml descriptors (web.xml/web-default.xml/web-override.xml) or web-fragments.
+        //Maintenance update 3.0a to spec:
+        //  Updated 8.2.3.g.v to say <servlet-mapping> elements are additive across web-fragments. 
+      
+        
+        String filter_name = node.getString("filter-name", false, true);
+        
+        Origin origin = context.getMetaData().getOrigin(filter_name+".filter.mappings");
+        
+        switch (origin)
+        {
+            case NotSet:
+            {
+                //no filtermappings for this filter yet defined
+                context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor);
+                addFilterMapping(filter_name, node, context, descriptor);
+                break;
+            }
+            case WebDefaults:
+            case WebOverride:
+            case WebXml:
+            {
+                //filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings.
+                if (!(descriptor instanceof FragmentDescriptor))
+                {
+                   addFilterMapping(filter_name, node, context, descriptor);
+                }
+                break;
+            }
+            case WebFragment:
+            {
+                //filter mappings first defined in a web-fragment, allow other fragments to add
+                addFilterMapping(filter_name, node, context, descriptor);
+                break;
+            }
+        }
+    }
+
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        String className = node.getString("listener-class", false, true);
+        EventListener listener = null;
+        try
+        {
+            if (className != null && className.length()> 0)
+            {
+                //Servlet Spec 3.0 p 74
+                //Duplicate listener declarations don't result in duplicate listener instances
+                EventListener[] listeners=context.getEventListeners();
+                if (listeners!=null)
+                {
+                    for (EventListener l : listeners)
+                    {
+                        if (l.getClass().getName().equals(className))
+                            return;
+                    }
+                }
+                
+                ((WebDescriptor)descriptor).addClassName(className);
+
+                Class<? extends EventListener> listenerClass = (Class<? extends EventListener>)context.loadClass(className);
+                listener = newListenerInstance(context,listenerClass);
+                if (!(listener instanceof EventListener))
+                {
+                    LOG.warn("Not an EventListener: " + listener);
+                    return;
+                }
+                context.addEventListener(listener);
+                context.getMetaData().setOrigin(className+".listener", descriptor);
+                
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Could not instantiate listener " + className, e);
+            return;
+        }
+    }
+    
+    /**
+     * @param context
+     * @param descriptor
+     * @param node
+     */
+    protected void visitDistributable(WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+    {
+        // the element has no content, so its simple presence
+        // indicates that the webapp is distributable...
+        //Servlet Spec 3.0 p.74  distributable only if all fragments are distributable
+        ((WebDescriptor)descriptor).setDistributable(true);
+    }
+    
+    /**
+     * @param context
+     * @param clazz
+     * @return the new event listener
+     * @throws ServletException
+     * @throws InstantiationException
+     * @throws IllegalAccessException
+     */
+    protected EventListener newListenerInstance(WebAppContext context,Class<? extends EventListener> clazz) throws ServletException, InstantiationException, IllegalAccessException
+    {
+        try
+        {
+            return context.getServletContext().createListener(clazz);
+        }
+        catch (ServletException se)
+        {
+            Throwable cause = se.getRootCause();
+            if (cause instanceof InstantiationException)
+                throw (InstantiationException)cause;
+            if (cause instanceof IllegalAccessException)
+                throw (IllegalAccessException)cause;
+            throw se;
+        }
+    }
+    
+    /**
+     * @param p
+     * @return the normalized pattern
+     */
+    protected String normalizePattern(String p)
+    {
+        if (p != null && p.length() > 0 && !p.startsWith("/") && !p.startsWith("*")) return "/" + p;
+        return p;
+    }
+
+  
+}
diff --git a/src/java/org/eclipse/jetty/webapp/TagLibConfiguration.java b/src/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
new file mode 100644
index 0000000..b545530
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/TagLibConfiguration.java
@@ -0,0 +1,530 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlParser;
+
+/* ------------------------------------------------------------ */
+/** TagLibConfiguration.
+ * 
+ * The class searches for TLD descriptors found in web.xml, in WEB-INF/*.tld files of the web app
+ * or *.tld files within jars found in WEB-INF/lib of the webapp.   Any listeners defined in these
+ * tld's are added to the context.
+ * 
+ * &lt;bile&gt;This is total rubbish special case for JSPs! If there was a general use-case for web app
+ * frameworks to register listeners directly, then a generic mechanism could have been added to the servlet
+ * spec.  Instead some special purpose JSP support is required that breaks all sorts of encapsulation rules as
+ * the servlet container must go searching for and then parsing the descriptors for one particular framework.
+ * It only appears to be used by JSF, which is being developed by the same developer who implemented this
+ * feature in the first place!
+ * &lt;/bile&gt;
+ * 
+ * 
+ * Note- this has been superceded by the new TldScanner in jasper which uses ServletContainerInitializer to
+ * find all the listeners in tag libs and register them.
+ */
+public class TagLibConfiguration extends AbstractConfiguration
+{
+    private static final Logger LOG = Log.getLogger(TagLibConfiguration.class);
+
+    public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds";
+    
+  
+    /**
+     * TagLibListener
+     *
+     * A listener that does the job of finding .tld files that contain
+     * (other) listeners that need to be called by the servlet container.
+     * 
+     * This implementation is necessitated by the fact that it is only
+     * after all the Configuration classes have run that we will
+     * parse web.xml/fragments etc and thus find tlds mentioned therein.
+     * 
+     * Note: TagLibConfiguration is not used in jetty-8 as jasper (JSP engine)
+     * uses the new TldScanner class - a ServletContainerInitializer from
+     * Servlet Spec 3 - to find all listeners in taglibs and register them
+     * with the servlet container.
+     */
+    public  class TagLibListener implements ServletContextListener {
+        private List<EventListener> _tldListeners;
+        private WebAppContext _context;       
+        
+        public TagLibListener (WebAppContext context) {
+            _context = context;
+        }
+
+        public void contextDestroyed(ServletContextEvent sce)
+        {
+            if (_tldListeners == null)
+                return;
+            
+            for (int i=_tldListeners.size()-1; i>=0; i--) {
+                EventListener l = _tldListeners.get(i);
+                if (l instanceof ServletContextListener) {
+                    ((ServletContextListener)l).contextDestroyed(sce);
+                }
+            }
+        }
+
+        public void contextInitialized(ServletContextEvent sce)
+        {
+            try 
+            {
+                //For jasper 2.1: 
+                //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
+                try
+                {
+
+                    ClassLoader loader = _context.getClassLoader();
+                    if (loader == null || loader.getParent() == null)
+                        loader = getClass().getClassLoader();
+                    else
+                        loader = loader.getParent();
+                    Class<?> clazz = loader.loadClass("org.apache.jasper.compiler.TldLocationsCache");
+                    assert clazz!=null;
+                    Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
+                   
+                    Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
+                    
+                    if (tld_resources != null)
+                    {
+                        //get the jar file names of the files
+                        for (Resource r:tld_resources)
+                        {
+                            Resource jarResource = extractJarResource(r);
+                            //jasper is happy with an empty list of tlds
+                            if (!tldMap.containsKey(jarResource.getURI()))
+                                tldMap.put(jarResource.getURI(), null);
+
+                        }
+                        //set the magic context attribute that tells jasper about the system tlds
+                        sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
+                    }
+                }
+                catch (ClassNotFoundException e)
+                {
+                    LOG.ignore(e);
+                }
+               
+                //find the tld files and parse them to get out their
+                //listeners
+                Set<Resource> tlds = findTldResources();
+                List<TldDescriptor> descriptors = parseTlds(tlds);
+                processTlds(descriptors);
+                
+                if (_tldListeners == null)
+                    return;
+                
+                //call the listeners that are ServletContextListeners, put the
+                //rest into the context's list of listeners to call at the appropriate
+                //moment
+                for (EventListener l:_tldListeners) {
+                    if (l instanceof ServletContextListener) {
+                        ((ServletContextListener)l).contextInitialized(sce);
+                    } else {
+                        _context.addEventListener(l);
+                    }
+                }
+                
+            } 
+            catch (Exception e) {
+                LOG.warn(e);
+            }
+        }
+
+
+        
+        
+        private Resource extractJarResource (Resource r)
+        {
+            if (r == null)
+                return null;
+            
+            try
+            {
+                String url = r.getURI().toURL().toString();
+                int idx = url.lastIndexOf("!/");
+                if (idx >= 0)
+                    url = url.substring(0, idx);
+                if (url.startsWith("jar:"))
+                    url = url.substring(4);
+                return Resource.newResource(url);
+            }
+            catch (IOException e)
+            {
+                LOG.warn(e);
+                return null;
+            }
+        }
+    
+        /**
+         * Find all the locations that can harbour tld files that may contain
+         * a listener which the web container is supposed to instantiate and
+         * call.
+         * 
+         * @return
+         * @throws IOException
+         */
+        private Set<Resource> findTldResources () throws IOException {
+            
+            Set<Resource> tlds = new HashSet<Resource>();
+            
+            // Find tld's from web.xml
+            // When web.xml was processed, it should have created aliases for all TLDs.  So search resources aliases
+            // for aliases ending in tld
+            if (_context.getResourceAliases()!=null && 
+                    _context.getBaseResource()!=null && 
+                    _context.getBaseResource().exists())
+            {
+                Iterator<String> iter=_context.getResourceAliases().values().iterator();
+                while(iter.hasNext())
+                {
+                    String location = iter.next();
+                    if (location!=null && location.toLowerCase(Locale.ENGLISH).endsWith(".tld"))
+                    {
+                        if (!location.startsWith("/"))
+                            location="/WEB-INF/"+location;
+                        Resource l=_context.getBaseResource().addPath(location);
+                        tlds.add(l);
+                    }
+                }
+            }
+            
+            // Look for any tlds in WEB-INF directly.
+            Resource web_inf = _context.getWebInf();
+            if (web_inf!=null)
+            {
+                String[] contents = web_inf.list();
+                for (int i=0;contents!=null && i<contents.length;i++)
+                {
+                    if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
+                    {
+                        Resource l=web_inf.addPath(contents[i]);
+                        tlds.add(l);
+                    }
+                }
+            }
+            
+            //Look for tlds in common location of WEB-INF/tlds
+            if (web_inf != null) {
+                Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
+                if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
+                    String[] contents = web_inf_tlds.list();
+                    for (int i=0;contents!=null && i<contents.length;i++)
+                    {
+                        if (contents[i]!=null && contents[i].toLowerCase(Locale.ENGLISH).endsWith(".tld"))
+                        {
+                            Resource l=web_inf_tlds.addPath(contents[i]);
+                            tlds.add(l);
+                        }
+                    }
+                } 
+            }
+
+            // Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
+            // the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
+            // and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
+            @SuppressWarnings("unchecked")
+            Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
+            if (tld_resources!=null)
+                tlds.addAll(tld_resources);
+            
+            return tlds;
+        }
+        
+        
+        /**
+         * Parse xml into in-memory tree
+         * @param tlds
+         * @return
+         */
+        private List<TldDescriptor> parseTlds (Set<Resource> tlds) {         
+            List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
+            
+            Resource tld = null;
+            Iterator<Resource> iter = tlds.iterator();
+            while (iter.hasNext())
+            {
+                try
+                {
+                    tld = iter.next();
+                    if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
+                   
+                    TldDescriptor d = new TldDescriptor(tld);
+                    d.parse();
+                    descriptors.add(d);
+                }
+                catch(Exception e)
+                {
+                    LOG.warn("Unable to parse TLD: " + tld,e);
+                }
+            }
+            return descriptors;
+        }
+        
+        
+        /**
+         * Create listeners from the parsed tld trees
+         * @param descriptors
+         * @throws Exception
+         */
+        private void processTlds (List<TldDescriptor> descriptors) throws Exception {
+
+            TldProcessor processor = new TldProcessor();
+            for (TldDescriptor d:descriptors)
+                processor.process(_context, d); 
+            
+            _tldListeners = new ArrayList<EventListener>(processor.getListeners());
+        }
+    }
+    
+    
+    
+    
+    /**
+     * TldDescriptor
+     *
+     *
+     */
+    public static class TldDescriptor extends Descriptor
+    {
+        protected static XmlParser __parserSingleton;
+
+        public TldDescriptor(Resource xml)
+        {
+            super(xml);
+        }
+
+        @Override
+        public void ensureParser() throws ClassNotFoundException
+        {
+           if (__parserSingleton == null)
+               __parserSingleton = newParser();
+            _parser = __parserSingleton;
+        }
+
+        @Override
+        public XmlParser newParser() throws ClassNotFoundException
+        {
+            // Create a TLD parser
+            XmlParser parser = new XmlParser(false);
+            
+            URL taglib11=null;
+            URL taglib12=null;
+            URL taglib20=null;
+            URL taglib21=null;
+
+            try
+            {
+                Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
+                taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
+                taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
+                taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
+                taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
+            }
+            catch(Exception e)
+            {
+                LOG.ignore(e);
+            }
+            finally
+            {
+                if(taglib11==null)
+                    taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd",true);
+                if(taglib12==null)
+                    taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd",true);
+                if(taglib20==null)
+                    taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd",true);
+                if(taglib21==null)
+                    taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd",true);
+            }
+            
+
+            if(taglib11!=null)
+            {
+                redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);  
+                redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
+            }
+            if(taglib12!=null)
+            {
+                redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
+                redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
+            }
+            if(taglib20!=null)
+            {
+                redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
+                redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
+            }
+            if(taglib21!=null)
+            {
+                redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
+                redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
+            }
+            
+            parser.setXpath("/taglib/listener/listener-class");
+            return parser;
+        }
+        
+        public void parse ()
+        throws Exception
+        {
+            ensureParser();
+            try
+            {
+                //xerces on apple appears to sometimes close the zip file instead
+                //of the inputstream, so try opening the input stream, but if
+                //that doesn't work, fallback to opening a new url
+                _root = _parser.parse(_xml.getInputStream());
+            }
+            catch (Exception e)
+            {
+                _root = _parser.parse(_xml.getURL().toString());
+            }
+
+            if (_root==null)
+            {
+                LOG.warn("No TLD root in {}",_xml);
+            }
+        }
+    }
+    
+    
+    /**
+     * TldProcessor
+     *
+     * Process TldDescriptors representing tag libs to find listeners.
+     */
+    public class TldProcessor extends IterativeDescriptorProcessor
+    {
+        public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
+        XmlParser _parser;
+        List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
+        List<EventListener> _listeners;
+        
+        
+        public TldProcessor ()
+        throws Exception
+        {  
+            _listeners = new ArrayList<EventListener>();
+            registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
+        }
+      
+
+        public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
+        {     
+            String className=node.getString("listener-class",false,true);
+            if (LOG.isDebugEnabled()) 
+                LOG.debug("listener="+className);
+
+            try
+            {
+                Class<?> listenerClass = context.loadClass(className);
+                EventListener l = (EventListener)listenerClass.newInstance();
+                _listeners.add(l);
+            }
+            catch(Exception e)
+            {
+                LOG.warn("Could not instantiate listener "+className+": "+e);
+                LOG.debug(e);
+            }
+            catch(Error e)
+            {
+                LOG.warn("Could not instantiate listener "+className+": "+e);
+                LOG.debug(e);
+            }
+
+        }
+
+        @Override
+        public void end(WebAppContext context, Descriptor descriptor)
+        {
+        }
+
+        @Override
+        public void start(WebAppContext context, Descriptor descriptor)
+        {  
+        }
+        
+        public List<EventListener> getListeners() {
+            return _listeners;
+        }
+    }
+
+
+    @Override
+    public void preConfigure(WebAppContext context) throws Exception
+    {
+        try
+        {
+            Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
+        }
+        catch (Exception e)
+        {
+            //no jsp available, don't parse TLDs
+            return;
+        }
+
+        TagLibListener tagLibListener = new TagLibListener(context);
+        context.addEventListener(tagLibListener);
+    }
+    
+
+    @Override
+    public void configure (WebAppContext context) throws Exception
+    {         
+    }
+
+    @Override
+    public void postConfigure(WebAppContext context) throws Exception
+    {     
+    }
+
+
+    @Override
+    public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
+    {
+    }
+
+
+    @Override
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+    } 
+}
diff --git a/src/java/org/eclipse/jetty/webapp/WebAppClassLoader.java b/src/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
new file mode 100644
index 0000000..c4abbc8
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/WebAppClassLoader.java
@@ -0,0 +1,449 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+
+
+/* ------------------------------------------------------------ */
+/** ClassLoader for HttpContext.
+ * Specializes URLClassLoader with some utility and file mapping
+ * methods.
+ *
+ * This loader defaults to the 2.3 servlet spec behavior where non
+ * system classes are loaded from the classpath in preference to the
+ * parent loader.  Java2 compliant loading, where the parent loader
+ * always has priority, can be selected with the 
+ * {@link org.eclipse.jetty.webapp.WebAppContext#setParentLoaderPriority(boolean)} 
+ * method and influenced with {@link WebAppContext#isServerClass(String)} and 
+ * {@link WebAppContext#isSystemClass(String)}.
+ *
+ * If no parent class loader is provided, then the current thread 
+ * context classloader will be used.  If that is null then the 
+ * classloader that loaded this class is used as the parent.
+ * 
+ */
+public class WebAppClassLoader extends URLClassLoader
+{
+    private static final Logger LOG = Log.getLogger(WebAppClassLoader.class);
+
+    private final Context _context;
+    private final ClassLoader _parent;
+    private final Set<String> _extensions=new HashSet<String>();
+    private String _name=String.valueOf(hashCode());
+    
+    /* ------------------------------------------------------------ */
+    /** The Context in which the classloader operates.
+     */
+    public interface Context
+    {
+        /* ------------------------------------------------------------ */
+        /** Convert a URL or path to a Resource.
+         * The default implementation
+         * is a wrapper for {@link Resource#newResource(String)}.
+         * @param urlOrPath The URL or path to convert
+         * @return The Resource for the URL/path
+         * @throws IOException The Resource could not be created.
+         */
+        Resource newResource(String urlOrPath) throws IOException;
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return Returns the permissions.
+         */
+        PermissionCollection getPermissions();
+
+        /* ------------------------------------------------------------ */
+        /** Is the class a System Class.
+         * A System class is a class that is visible to a webapplication,
+         * but that cannot be overridden by the contents of WEB-INF/lib or
+         * WEB-INF/classes 
+         * @param clazz The fully qualified name of the class.
+         * @return True if the class is a system class.
+         */
+        boolean isSystemClass(String clazz);
+
+        /* ------------------------------------------------------------ */
+        /** Is the class a Server Class.
+         * A Server class is a class that is part of the implementation of 
+         * the server and is NIT visible to a webapplication. The web
+         * application may provide it's own implementation of the class,
+         * to be loaded from WEB-INF/lib or WEB-INF/classes 
+         * @param clazz The fully qualified name of the class.
+         * @return True if the class is a server class.
+         */
+        boolean isServerClass(String clazz);
+
+        /* ------------------------------------------------------------ */
+        /**
+         * @return True if the classloader should delegate first to the parent 
+         * classloader (standard java behaviour) or false if the classloader 
+         * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet 
+         * spec recommendation).
+         */
+        boolean isParentLoaderPriority();
+        
+        /* ------------------------------------------------------------ */
+        String getExtraClasspath();
+        
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public WebAppClassLoader(Context context)
+        throws IOException
+    {
+        this(null,context);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Constructor.
+     */
+    public WebAppClassLoader(ClassLoader parent, Context context)
+        throws IOException
+    {
+        super(new URL[]{},parent!=null?parent
+                :(Thread.currentThread().getContextClassLoader()!=null?Thread.currentThread().getContextClassLoader()
+                        :(WebAppClassLoader.class.getClassLoader()!=null?WebAppClassLoader.class.getClassLoader()
+                                :ClassLoader.getSystemClassLoader())));
+        _parent=getParent();
+        _context=context;
+        if (_parent==null)
+            throw new IllegalArgumentException("no parent classloader!");
+        
+        _extensions.add(".jar");
+        _extensions.add(".zip");
+        
+        // TODO remove this system property
+        String extensions = System.getProperty(WebAppClassLoader.class.getName() + ".extensions");
+        if(extensions!=null)
+        {
+            StringTokenizer tokenizer = new StringTokenizer(extensions, ",;");
+            while(tokenizer.hasMoreTokens())
+                _extensions.add(tokenizer.nextToken().trim());
+        }
+        
+        if (context.getExtraClasspath()!=null)
+            addClassPath(context.getExtraClasspath());
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the name of the classloader
+     */
+    public String getName()
+    {
+        return _name;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name the name of the classloader
+     */
+    public void setName(String name)
+    {
+        _name=name;
+    }
+    
+
+    /* ------------------------------------------------------------ */
+    public Context getContext()
+    {
+        return _context;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param resource Comma or semicolon separated path of filenames or URLs
+     * pointing to directories or jar files. Directories should end
+     * with '/'.
+     */
+    public void addClassPath(Resource resource)
+        throws IOException
+    {
+        if (resource instanceof ResourceCollection)
+        {
+            for (Resource r : ((ResourceCollection)resource).getResources())
+                addClassPath(r);
+        }
+        else
+        {
+            addClassPath(resource.toString());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param classPath Comma or semicolon separated path of filenames or URLs
+     * pointing to directories or jar files. Directories should end
+     * with '/'.
+     */
+    public void addClassPath(String classPath)
+    	throws IOException
+    {
+        if (classPath == null)
+            return;
+            
+        StringTokenizer tokenizer= new StringTokenizer(classPath, ",;");
+        while (tokenizer.hasMoreTokens())
+        {
+            Resource resource= _context.newResource(tokenizer.nextToken().trim());
+            if (LOG.isDebugEnabled())
+                LOG.debug("Path resource=" + resource);
+
+            // Add the resource
+            if (resource.isDirectory() && resource instanceof ResourceCollection)
+                addClassPath(resource);
+            else
+            {
+                // Resolve file path if possible
+                File file= resource.getFile();
+                if (file != null)
+                {
+                    URL url= resource.getURL();
+                    addURL(url);
+                }
+                else if (resource.isDirectory())
+                    addURL(resource.getURL());
+                else
+                    throw new IllegalArgumentException("!file: "+resource);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param file Checks if this file type can be added to the classpath.
+     */
+    private boolean isFileSupported(String file)
+    {
+        int dot = file.lastIndexOf('.');
+        return dot!=-1 && _extensions.contains(file.substring(dot));
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Add elements to the class path for the context from the jar and zip files found
+     *  in the specified resource.
+     * @param lib the resource that contains the jar and/or zip files.
+     */
+    public void addJars(Resource lib)
+    {
+        if (lib.exists() && lib.isDirectory())
+        {
+            String[] files=lib.list();
+            for (int f=0;files!=null && f<files.length;f++)
+            {
+                try 
+                {
+                    Resource fn=lib.addPath(files[f]);
+                    String fnlc=fn.getName().toLowerCase(Locale.ENGLISH);
+                    // don't check if this is a directory, see Bug 353165
+                    if (isFileSupported(fnlc))
+                    {
+                        String jar=fn.toString();
+                        jar=StringUtil.replace(jar, ",", "%2C");
+                        jar=StringUtil.replace(jar, ";", "%3B");
+                        addClassPath(jar);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    LOG.warn(Log.EXCEPTION,ex);
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public PermissionCollection getPermissions(CodeSource cs)
+    {
+        // TODO check CodeSource
+        PermissionCollection permissions=_context.getPermissions();
+        PermissionCollection pc= (permissions == null) ? super.getPermissions(cs) : permissions;
+        return pc;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Enumeration<URL> getResources(String name) throws IOException
+    {
+        boolean system_class=_context.isSystemClass(name);
+        boolean server_class=_context.isServerClass(name);
+        
+        List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
+        List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
+            
+        if (_context.isParentLoaderPriority())
+        {
+            from_parent.addAll(from_webapp);
+            return Collections.enumeration(from_parent);
+        }
+        from_webapp.addAll(from_parent);
+        return Collections.enumeration(from_webapp);
+    }
+
+    /* ------------------------------------------------------------ */
+    private List<URL> toList(Enumeration<URL> e)
+    {
+        if (e==null)
+            return new ArrayList<URL>();
+        return Collections.list(e);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a resource from the classloader
+     * 
+     * NOTE: this method provides a convenience of hacking off a leading /
+     * should one be present. This is non-standard and it is recommended 
+     * to not rely on this behavior
+     */
+    public URL getResource(String name)
+    {
+        URL url= null;
+        boolean tried_parent= false;
+        boolean system_class=_context.isSystemClass(name);
+        boolean server_class=_context.isServerClass(name);
+        
+        if (system_class && server_class)
+            return null;
+        
+        if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
+        {
+            tried_parent= true;
+            
+            if (_parent!=null)
+                url= _parent.getResource(name);
+        }
+
+        if (url == null)
+        {
+            url= this.findResource(name);
+
+            if (url == null && name.startsWith("/"))
+            {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("HACK leading / off " + name);
+                url= this.findResource(name.substring(1));
+            }
+        }
+
+        if (url == null && !tried_parent && !server_class )
+        {
+            if (_parent!=null)
+                url= _parent.getResource(name);
+        }
+
+        if (url != null)
+            if (LOG.isDebugEnabled())
+                LOG.debug("getResource("+name+")=" + url);
+
+        return url;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException
+    {
+        return loadClass(name, false);
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+    {
+        Class<?> c= findLoadedClass(name);
+        ClassNotFoundException ex= null;
+        boolean tried_parent= false;
+        
+        boolean system_class=_context.isSystemClass(name);
+        boolean server_class=_context.isServerClass(name);
+        
+        if (system_class && server_class)
+        {
+            return null;
+        }
+        
+        if (c == null && _parent!=null && (_context.isParentLoaderPriority() || system_class) && !server_class)
+        {
+            tried_parent= true;
+            try
+            {
+                c= _parent.loadClass(name);
+                if (LOG.isDebugEnabled())
+                    LOG.debug("loaded " + c);
+            }
+            catch (ClassNotFoundException e)
+            {
+                ex= e;
+            }
+        }
+
+        if (c == null)
+        {
+            try
+            {
+                c= this.findClass(name);
+            }
+            catch (ClassNotFoundException e)
+            {
+                ex= e;
+            }
+        }
+
+        if (c == null && _parent!=null && !tried_parent && !server_class )
+            c= _parent.loadClass(name);
+
+        if (c == null)
+            throw ex;
+
+        if (resolve)
+            resolveClass(c);
+
+        if (LOG.isDebugEnabled())
+            LOG.debug("loaded " + c+ " from "+c.getClassLoader());
+        
+        return c;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String toString()
+    {
+        return "WebAppClassLoader=" + _name+"@"+Long.toHexString(hashCode());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/WebAppContext.java b/src/java/org/eclipse/jetty/webapp/WebAppContext.java
new file mode 100644
index 0000000..0caec5b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/WebAppContext.java
@@ -0,0 +1,1377 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.PermissionCollection;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.HttpMethodConstraintElement;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRegistration.Dynamic;
+import javax.servlet.ServletSecurityElement;
+import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
+import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
+import javax.servlet.http.HttpSessionActivationListener;
+import javax.servlet.http.HttpSessionAttributeListener;
+import javax.servlet.http.HttpSessionBindingListener;
+import javax.servlet.http.HttpSessionListener;
+
+import org.eclipse.jetty.security.ConstraintAware;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HandlerContainer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.MultiException;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.util.security.Constraint;
+
+/* ------------------------------------------------------------ */
+/** Web Application Context Handler.
+ * The WebAppContext handler is an extension of ContextHandler that
+ * coordinates the construction and configuration of nested handlers:
+ * {@link org.eclipse.jetty.security.ConstraintSecurityHandler}, {@link org.eclipse.jetty.server.session.SessionHandler}
+ * and {@link org.eclipse.jetty.servlet.ServletHandler}.
+ * The handlers are configured by pluggable configuration classes, with
+ * the default being  {@link org.eclipse.jetty.webapp.WebXmlConfiguration} and
+ * {@link org.eclipse.jetty.webapp.JettyWebXmlConfiguration}.
+ *
+ * @org.apache.xbean.XBean description="Creates a servlet web application at a given context from a resource base"
+ *
+ */
+public class WebAppContext extends ServletContextHandler implements WebAppClassLoader.Context
+{
+    private static final Logger LOG = Log.getLogger(WebAppContext.class);
+
+    public static final String TEMPDIR = "javax.servlet.context.tempdir";
+    public static final String BASETEMPDIR = "org.eclipse.jetty.webapp.basetempdir";
+    public final static String WEB_DEFAULTS_XML="org/eclipse/jetty/webapp/webdefault.xml";
+    public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page";
+    public final static String SERVER_CONFIG = "org.eclipse.jetty.webapp.configuration";
+    public final static String SERVER_SYS_CLASSES = "org.eclipse.jetty.webapp.systemClasses";
+    public final static String SERVER_SRV_CLASSES = "org.eclipse.jetty.webapp.serverClasses";
+    
+    private String[] __dftProtectedTargets = {"/web-inf", "/meta-inf"};
+    
+    private static String[] __dftConfigurationClasses =
+    {
+        "org.eclipse.jetty.webapp.WebInfConfiguration",
+        "org.eclipse.jetty.webapp.WebXmlConfiguration",
+        "org.eclipse.jetty.webapp.MetaInfConfiguration",
+        "org.eclipse.jetty.webapp.FragmentConfiguration",
+        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"//,
+        //"org.eclipse.jetty.webapp.TagLibConfiguration"
+    } ;
+
+    // System classes are classes that cannot be replaced by
+    // the web application, and they are *always* loaded via
+    // system classloader.
+    public final static String[] __dftSystemClasses =
+    {
+        "java.",                            // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+        "javax.",                           // Java SE classes (per servlet spec v2.5 / SRV.9.7.2)
+        "org.xml.",                         // needed by javax.xml
+        "org.w3c.",                         // needed by javax.xml
+        "org.apache.commons.logging.",      // TODO: review if special case still needed
+        "org.eclipse.jetty.continuation.",  // webapp cannot change continuation classes
+        "org.eclipse.jetty.jndi.",          // webapp cannot change naming classes
+        "org.eclipse.jetty.plus.jaas.",     // webapp cannot change jaas classes
+        "org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
+        "org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
+        "org.eclipse.jetty.websocket.WebSocketServlet", // webapp cannot change WebSocketServlet
+        "org.eclipse.jetty.servlet.DefaultServlet" // webapp cannot change default servlets
+    } ;
+
+    // Server classes are classes that are hidden from being
+    // loaded by the web application using system classloader,
+    // so if web application needs to load any of such classes,
+    // it has to include them in its distribution.
+    public final static String[] __dftServerClasses =
+    {
+        "-org.eclipse.jetty.continuation.", // don't hide continuation classes
+        "-org.eclipse.jetty.jndi.",         // don't hide naming classes
+        "-org.eclipse.jetty.plus.jaas.",    // don't hide jaas classes
+        "-org.eclipse.jetty.websocket.WebSocket", // WebSocket is a jetty extension
+        "-org.eclipse.jetty.websocket.WebSocketFactory", // WebSocket is a jetty extension
+        "-org.eclipse.jetty.websocket.WebSocketServlet", // don't hide WebSocketServlet
+        "-org.eclipse.jetty.servlet.DefaultServlet", // don't hide default servlet
+        "-org.eclipse.jetty.servlet.listener.", // don't hide useful listeners
+        "org.eclipse.jetty."                // hide other jetty classes
+    } ;
+
+    private String[] _configurationClasses = __dftConfigurationClasses;
+    private ClasspathPattern _systemClasses = null;
+    private ClasspathPattern _serverClasses = null;
+
+    private Configuration[] _configurations;
+    private String _defaultsDescriptor=WEB_DEFAULTS_XML;
+    private String _descriptor=null;
+    private final List<String> _overrideDescriptors = new ArrayList<String>();
+    private boolean _distributable=false;
+    private boolean _extractWAR=true;
+    private boolean _copyDir=false;
+    private boolean _copyWebInf=false; // TODO change to true?
+    private boolean _logUrlOnStart =false;
+    private boolean _parentLoaderPriority= Boolean.getBoolean("org.eclipse.jetty.server.webapp.parentLoaderPriority");
+    private PermissionCollection _permissions;
+
+    private String[] _contextWhiteList = null;
+
+    private File _tmpDir;
+    private String _war;
+    private String _extraClasspath;
+    private Throwable _unavailableException;
+
+    private Map<String, String> _resourceAliases;
+    private boolean _ownClassLoader=false;
+    private boolean _configurationDiscovered=true;
+    private boolean _configurationClassesSet=false;
+    private boolean _configurationsSet=false;
+    private boolean _allowDuplicateFragmentNames = false;
+    private boolean _throwUnavailableOnStartupException = false;
+    
+    
+
+    private MetaData _metadata=new MetaData();
+
+    public static WebAppContext getCurrentWebAppContext()
+    {
+        ContextHandler.Context context=ContextHandler.getCurrentContext();
+        if (context!=null)
+        {
+            ContextHandler handler = context.getContextHandler();
+            if (handler instanceof WebAppContext)
+                return (WebAppContext)handler;
+        }
+        return null;
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebAppContext()
+    {
+        super(SESSIONS|SECURITY);
+        _scontext=new Context();
+        setErrorHandler(new ErrorPageErrorHandler());
+        setProtectedTargets(__dftProtectedTargets);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param contextPath The context path
+     * @param webApp The URL or filename of the webapp directory or war file.
+     */
+    public WebAppContext(String webApp,String contextPath)
+    {
+        super(null,contextPath,SESSIONS|SECURITY);
+        _scontext=new Context();
+        setContextPath(contextPath);
+        setWar(webApp);
+        setErrorHandler(new ErrorPageErrorHandler());
+        setProtectedTargets(__dftProtectedTargets);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param parent The parent HandlerContainer.
+     * @param contextPath The context path
+     * @param webApp The URL or filename of the webapp directory or war file.
+     */
+    public WebAppContext(HandlerContainer parent, String webApp, String contextPath)
+    {
+        super(parent,contextPath,SESSIONS|SECURITY);
+        _scontext=new Context();
+        setWar(webApp);
+        setErrorHandler(new ErrorPageErrorHandler());
+        setProtectedTargets(__dftProtectedTargets);
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * This constructor is used in the geronimo integration.
+     *
+     * @param sessionHandler SessionHandler for this web app
+     * @param securityHandler SecurityHandler for this web app
+     * @param servletHandler ServletHandler for this web app
+     * @param errorHandler ErrorHandler for this web app
+     */
+    public WebAppContext(SessionHandler sessionHandler, SecurityHandler securityHandler, ServletHandler servletHandler, ErrorHandler errorHandler) {
+        super(null, sessionHandler, securityHandler, servletHandler, errorHandler);
+        _scontext = new Context();
+        setErrorHandler(errorHandler != null ? errorHandler : new ErrorPageErrorHandler());
+        setProtectedTargets(__dftProtectedTargets);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param servletContextName The servletContextName to set.
+     */
+    @Override
+    public void setDisplayName(String servletContextName)
+    {
+        super.setDisplayName(servletContextName);
+        ClassLoader cl = getClassLoader();
+        if (cl!=null && cl instanceof WebAppClassLoader && servletContextName!=null)
+            ((WebAppClassLoader)cl).setName(servletContextName);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Get an exception that caused the webapp to be unavailable
+     * @return A throwable if the webapp is unavailable or null
+     */
+    public Throwable getUnavailableException()
+    {
+        return _unavailableException;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Set Resource Alias.
+     * Resource aliases map resource uri's within a context.
+     * They may optionally be used by a handler when looking for
+     * a resource.
+     * @param alias
+     * @param uri
+     */
+    public void setResourceAlias(String alias, String uri)
+    {
+        if (_resourceAliases == null)
+            _resourceAliases= new HashMap<String, String>(5);
+        _resourceAliases.put(alias, uri);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Map<String, String> getResourceAliases()
+    {
+        if (_resourceAliases == null)
+            return null;
+        return _resourceAliases;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void setResourceAliases(Map<String, String> map)
+    {
+        _resourceAliases = map;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getResourceAlias(String path)
+    {
+        if (_resourceAliases == null)
+            return null;
+        String alias = _resourceAliases.get(path);
+        
+        int slash=path.length();
+        while (alias==null)
+        {
+            slash=path.lastIndexOf("/",slash-1);
+            if (slash<0)
+                break;
+            String match=_resourceAliases.get(path.substring(0,slash+1));
+            if (match!=null)
+                alias=match+path.substring(slash+1);            
+        }
+        return alias;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String removeResourceAlias(String alias)
+    {
+        if (_resourceAliases == null)
+            return null;
+        return _resourceAliases.remove(alias);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.server.server.handler.ContextHandler#setClassLoader(java.lang.ClassLoader)
+     */
+    @Override
+    public void setClassLoader(ClassLoader classLoader)
+    {
+        super.setClassLoader(classLoader);
+
+//        if ( !(classLoader instanceof WebAppClassLoader) )
+//        {
+//            LOG.info("NOTE: detected a classloader which is not an instance of WebAppClassLoader being set on WebAppContext, some typical class and resource locations may be missing on: " + toString() );
+//        }
+
+        if (classLoader!=null && classLoader instanceof WebAppClassLoader && getDisplayName()!=null)
+            ((WebAppClassLoader)classLoader).setName(getDisplayName());
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public Resource getResource(String uriInContext) throws MalformedURLException
+    {
+        if (uriInContext==null || !uriInContext.startsWith(URIUtil.SLASH))
+            throw new MalformedURLException(uriInContext);
+
+        IOException ioe= null;
+        Resource resource= null;
+        int loop=0;
+        while (uriInContext!=null && loop++<100)
+        {
+            try
+            {
+                resource= super.getResource(uriInContext);
+                if (resource != null && resource.exists())
+                    return resource;
+
+                uriInContext = getResourceAlias(uriInContext);
+            }
+            catch (IOException e)
+            {
+                LOG.ignore(e);
+                if (ioe==null)
+                    ioe= e;
+            }
+        }
+
+        if (ioe != null && ioe instanceof MalformedURLException)
+            throw (MalformedURLException)ioe;
+
+        return resource;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Is the context Automatically configured.
+     *
+     * @return true if configuration discovery.
+     */
+    public boolean isConfigurationDiscovered()
+    {
+        return _configurationDiscovered;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Set the configuration discovery mode.
+     * If configuration discovery is set to true, then the JSR315
+     * servlet 3.0 discovered configuration features are enabled.
+     * These are:<ul>
+     * <li>Web Fragments</li>
+     * <li>META-INF/resource directories</li>
+     * </ul>
+     * @param discovered true if configuration discovery is enabled for automatic configuration from the context
+     */
+    public void setConfigurationDiscovered(boolean discovered)
+    {
+        _configurationDiscovered = discovered;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Pre configure the web application.
+     * <p>
+     * The method is normally called from {@link #start()}. It performs
+     * the discovery of the configurations to be applied to this context,
+     * specifically:<ul>
+     * <li>Instantiate the {@link Configuration} instances with a call to {@link #loadConfigurations()}.
+     * <li>Setup the default System classes by calling {@link #loadSystemClasses()}
+     * <li>Setup the default Server classes by calling <code>loadServerClasses()</code>
+     * <li>Instantiates a classload (if one is not already set)
+     * <li>Calls the {@link Configuration#preConfigure(WebAppContext)} method of all
+     * Configuration instances.
+     * </ul>
+     * @throws Exception
+     */
+    public void preConfigure() throws Exception
+    {
+        // Setup configurations
+        loadConfigurations();
+
+        // Setup system classes
+        loadSystemClasses();
+
+        // Setup server classes
+        loadServerClasses();
+
+        // Configure classloader
+        _ownClassLoader=false;
+        if (getClassLoader()==null)
+        {
+            WebAppClassLoader classLoader = new WebAppClassLoader(this);
+            setClassLoader(classLoader);
+            _ownClassLoader=true;
+        }
+
+        if (LOG.isDebugEnabled())
+        {
+            ClassLoader loader = getClassLoader();
+            LOG.debug("Thread Context classloader {}",loader);
+            loader=loader.getParent();
+            while(loader!=null)
+            {
+                LOG.debug("Parent class loader: {} ",loader);
+                loader=loader.getParent();
+            }
+        }
+
+        // Prepare for configuration
+        for (int i=0;i<_configurations.length;i++)
+        {
+            LOG.debug("preConfigure {} with {}",this,_configurations[i]);
+            _configurations[i].preConfigure(this);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void configure() throws Exception
+    {
+        // Configure webapp
+        for (int i=0;i<_configurations.length;i++)
+        {
+            LOG.debug("configure {} with {}",this,_configurations[i]);
+            _configurations[i].configure(this);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void postConfigure() throws Exception
+    {
+        // Clean up after configuration
+        for (int i=0;i<_configurations.length;i++)
+        {
+            LOG.debug("postConfigure {} with {}",this,_configurations[i]);
+            _configurations[i].postConfigure(this);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStart()
+     */
+    @Override
+    protected void doStart() throws Exception
+    {
+        try
+        {
+            _metadata.setAllowDuplicateFragmentNames(isAllowDuplicateFragmentNames());
+            preConfigure();
+            super.doStart();
+            postConfigure();
+
+            if (isLogUrlOnStart())
+                dumpUrl();
+        }
+        catch (Exception e)
+        {
+            //start up of the webapp context failed, make sure it is not started
+            LOG.warn("Failed startup of context "+this, e);
+            _unavailableException=e;
+            setAvailable(false);
+            if (isThrowUnavailableOnStartupException())
+                throw e;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /*
+     * @see org.eclipse.thread.AbstractLifeCycle#doStop()
+     */
+    @Override
+    protected void doStop() throws Exception
+    {
+        super.doStop();
+
+        try
+        {
+            for (int i=_configurations.length;i-->0;)
+                _configurations[i].deconfigure(this);
+
+            if (_metadata != null)
+                _metadata.clear();
+            _metadata=new MetaData();
+
+        }
+        finally
+        {
+            if (_ownClassLoader)
+                setClassLoader(null);
+
+            setAvailable(true);
+            _unavailableException=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        // Prepare for configuration
+        MultiException mx=new MultiException();
+        if (_configurations!=null)
+        {
+            for (int i=_configurations.length;i-->0;)
+            {
+                try
+                {
+                    _configurations[i].destroy(this);
+                }
+                catch(Exception e)
+                {
+                    mx.add(e);
+                }
+            }
+        }
+        _configurations=null;
+        super.destroy();
+        mx.ifExceptionThrowRuntime();
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /*
+     * Dumps the current web app name and URL to the log
+     */
+    private void dumpUrl()
+    {
+        Connector[] connectors = getServer().getConnectors();
+        for (int i=0;i<connectors.length;i++)
+        {
+            String connectorName = connectors[i].getName();
+            String displayName = getDisplayName();
+            if (displayName == null)
+                displayName = "WebApp@"+connectors.hashCode();
+
+            LOG.info(displayName + " at http://" + connectorName + getContextPath());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the configurations.
+     */
+    public String[] getConfigurationClasses()
+    {
+        return _configurationClasses;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the configurations.
+     */
+    public Configuration[] getConfigurations()
+    {
+        return _configurations;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
+     * @return Returns the defaultsDescriptor.
+     */
+    public String getDefaultsDescriptor()
+    {
+        return _defaultsDescriptor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
+     * @return Returns the Override Descriptor.
+     * @deprecated use {@link #getOverrideDescriptors()}
+     */
+    @Deprecated
+    public String getOverrideDescriptor()
+    {
+        if (_overrideDescriptors.size()!=1)
+            return null;
+        return _overrideDescriptors.get(0);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * An override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
+     * @return Returns the Override Descriptor list
+     */
+    public List<String> getOverrideDescriptors()
+    {
+        return Collections.unmodifiableList(_overrideDescriptors);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the permissions.
+     */
+    public PermissionCollection getPermissions()
+    {
+        return _permissions;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see #setServerClasses(String[])
+     * @return Returns the serverClasses.
+     */
+    public String[] getServerClasses()
+    {
+        if (_serverClasses == null)
+            loadServerClasses();
+
+        return _serverClasses.getPatterns();
+    }
+
+    public void addServerClass(String classname)
+    {
+        if (_serverClasses == null)
+            loadServerClasses();
+
+        _serverClasses.addPattern(classname);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see #setSystemClasses(String[])
+     * @return Returns the systemClasses.
+     */
+    public String[] getSystemClasses()
+    {
+        if (_systemClasses == null)
+            loadSystemClasses();
+
+        return _systemClasses.getPatterns();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void addSystemClass(String classname)
+    {
+        if (_systemClasses == null)
+            loadSystemClasses();
+
+        _systemClasses.addPattern(classname);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isServerClass(String name)
+    {
+        if (_serverClasses == null)
+            loadServerClasses();
+
+        return _serverClasses.match(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSystemClass(String name)
+    {
+        if (_systemClasses == null)
+            loadSystemClasses();
+
+        return _systemClasses.match(name);
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void loadSystemClasses()
+    {
+        if (_systemClasses != null)
+            return;
+
+        //look for a Server attribute with the list of System classes
+        //to apply to every web application. If not present, use our defaults.
+        Server server = getServer();
+        if (server != null)
+        {
+            Object systemClasses = server.getAttribute(SERVER_SYS_CLASSES);
+            if (systemClasses != null && systemClasses instanceof String[])
+                _systemClasses = new ClasspathPattern((String[])systemClasses);
+        }
+
+        if (_systemClasses == null)
+            _systemClasses = new ClasspathPattern(__dftSystemClasses);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void loadServerClasses()
+    {
+        if (_serverClasses != null)
+        {
+            return;
+        }
+
+        // look for a Server attribute with the list of Server classes
+        // to apply to every web application. If not present, use our defaults.
+        Server server = getServer();
+        if (server != null)
+        {
+            Object serverClasses = server.getAttribute(SERVER_SRV_CLASSES);
+            if (serverClasses != null && serverClasses instanceof String[])
+            {
+                _serverClasses = new ClasspathPattern((String[])serverClasses);
+            }
+        }
+
+        if (_serverClasses == null)
+        {
+            _serverClasses = new ClasspathPattern(__dftServerClasses);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the war as a file or URL string (Resource)
+     */
+    public String getWar()
+    {
+        if (_war==null)
+            _war=getResourceBase();
+        return _war;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Resource getWebInf() throws IOException
+    {
+        if (super.getBaseResource() == null)
+            return null;
+
+        // Iw there a WEB-INF directory?
+        Resource web_inf= super.getBaseResource().addPath("WEB-INF/");
+        if (!web_inf.exists() || !web_inf.isDirectory())
+            return null;
+
+        return web_inf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the distributable.
+     */
+    public boolean isDistributable()
+    {
+        return _distributable;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Returns the extractWAR.
+     */
+    public boolean isExtractWAR()
+    {
+        return _extractWAR;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the webdir is copied (to allow hot replacement of jars on windows)
+     */
+    public boolean isCopyWebDir()
+    {
+        return _copyDir;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
+     */
+    public boolean isCopyWebInf()
+    {
+        return _copyWebInf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if the classloader should delegate first to the parent
+     * classloader (standard java behaviour) or false if the classloader
+     * should first try to load from WEB-INF/lib or WEB-INF/classes (servlet
+     * spec recommendation).
+     */
+    public boolean isParentLoaderPriority()
+    {
+        return _parentLoaderPriority;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public String[] getDefaultConfigurationClasses ()
+    {
+        return __dftConfigurationClasses;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String[] getDefaultServerClasses ()
+    {
+        return __dftServerClasses;
+    }
+
+    /* ------------------------------------------------------------ */
+    public String[] getDefaultSystemClasses ()
+    {
+        return __dftSystemClasses;
+    }
+
+    /* ------------------------------------------------------------ */
+    protected void loadConfigurations()
+    	throws Exception
+    {
+        //if the configuration instances have been set explicitly, use them
+        if (_configurations!=null)
+            return;
+
+        //if the configuration classnames have been set explicitly use them
+        if (!_configurationClassesSet)
+            _configurationClasses=__dftConfigurationClasses;
+
+        _configurations = new Configuration[_configurationClasses.length];
+        for (int i = 0; i < _configurationClasses.length; i++)
+        {
+            _configurations[i]=(Configuration)Loader.loadClass(this.getClass(), _configurationClasses[i]).newInstance();
+        }
+    }
+
+  
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return super.toString()+(_war==null?"":(","+_war));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param configurations The configuration class names.  If setConfigurations is not called
+     * these classes are used to create a configurations array.
+     */
+    public void setConfigurationClasses(String[] configurations)
+    {
+        if (isRunning())
+            throw new IllegalStateException();
+        _configurationClasses = configurations==null?null:(String[])configurations.clone();
+        _configurationClassesSet = true;
+        _configurations=null;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param configurations The configurations to set.
+     */
+    public void setConfigurations(Configuration[] configurations)
+    {
+        if (isRunning())
+            throw new IllegalStateException();
+        _configurations = configurations==null?null:(Configuration[])configurations.clone();
+        _configurationsSet = true;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The default descriptor is a web.xml format file that is applied to the context before the standard WEB-INF/web.xml
+     * @param defaultsDescriptor The defaultsDescriptor to set.
+     */
+    public void setDefaultsDescriptor(String defaultsDescriptor)
+    {
+        _defaultsDescriptor = defaultsDescriptor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
+     * @param overrideDescriptor The overrideDescritpor to set.
+     * @deprecated use {@link #setOverrideDescriptors(List)}
+     */
+    @Deprecated
+    public void setOverrideDescriptor(String overrideDescriptor)
+    {
+        _overrideDescriptors.clear();
+        _overrideDescriptors.add(overrideDescriptor);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
+     * @param overrideDescriptors The overrideDescriptors (file or URL) to set.
+     */
+    public void setOverrideDescriptors(List<String> overrideDescriptors)
+    {
+        _overrideDescriptors.clear();
+        _overrideDescriptors.addAll(overrideDescriptors);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * The override descriptor is a web.xml format file that is applied to the context after the standard WEB-INF/web.xml
+     * @param overrideDescriptor The overrideDescriptor (file or URL) to add.
+     */
+    public void addOverrideDescriptor(String overrideDescriptor)
+    {
+        _overrideDescriptors.add(overrideDescriptor);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
+     */
+    public String getDescriptor()
+    {
+        return _descriptor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param descriptor the web.xml descriptor to use. If set to null, WEB-INF/web.xml is used if it exists.
+     */
+    public void setDescriptor(String descriptor)
+    {
+        _descriptor=descriptor;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param distributable The distributable to set.
+     */
+    public void setDistributable(boolean distributable)
+    {
+        this._distributable = distributable;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setEventListeners(EventListener[] eventListeners)
+    {
+        if (_sessionHandler!=null)
+            _sessionHandler.clearEventListeners();
+
+        super.setEventListeners(eventListeners);
+
+        for (int i=0; eventListeners!=null && i<eventListeners.length;i ++)
+        {
+            EventListener listener = eventListeners[i];
+
+            if ((listener instanceof HttpSessionActivationListener)
+                            || (listener instanceof HttpSessionAttributeListener)
+                            || (listener instanceof HttpSessionBindingListener)
+                            || (listener instanceof HttpSessionListener))
+            {
+                if (_sessionHandler!=null)
+                    _sessionHandler.addEventListener(listener);
+            }
+
+        }
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param extractWAR True if war files are extracted
+     */
+    public void setExtractWAR(boolean extractWAR)
+    {
+        _extractWAR = extractWAR;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param copy True if the webdir is copied (to allow hot replacement of jars)
+     */
+    public void setCopyWebDir(boolean copy)
+    {
+        _copyDir = copy;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param copyWebInf True if the web-inf lib and classes directories are copied (to allow hot replacement of jars on windows)
+     */
+    public void setCopyWebInf(boolean copyWebInf)
+    {
+        _copyWebInf = copyWebInf;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param java2compliant The java2compliant to set.
+     */
+    public void setParentLoaderPriority(boolean java2compliant)
+    {
+        _parentLoaderPriority = java2compliant;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param permissions The permissions to set.
+     */
+    public void setPermissions(PermissionCollection permissions)
+    {
+        _permissions = permissions;
+    }
+
+    /**
+     * Set the context white list
+     *
+     * In certain circumstances you want may want to deny access of one webapp from another
+     * when you may not fully trust the webapp.  Setting this white list will enable a
+     * check when a servlet called getContext(String), validating that the uriInPath
+     * for the given webapp has been declaratively allows access to the context.
+     * @param contextWhiteList
+     */
+    public void setContextWhiteList(String[] contextWhiteList)
+    {
+        _contextWhiteList = contextWhiteList;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the server classes patterns.
+     * <p>
+     * Server classes/packages are classes used to implement the server and are hidden
+     * from the context.  If the context needs to load these classes, it must have its
+     * own copy of them in WEB-INF/lib or WEB-INF/classes.
+     * A class pattern is a string of one of the forms:<dl>
+     * <dt>org.package.Classname</dt><dd>Match a specific class</dd>
+     * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd>
+     * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd>
+     * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd>
+     * </dl>
+     * @param serverClasses The serverClasses to set.
+     */
+    public void setServerClasses(String[] serverClasses)
+    {
+        _serverClasses = new ClasspathPattern(serverClasses);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the system classes patterns.
+     * <p>
+     * System classes/packages are classes provided by the JVM and that
+     * cannot be replaced by classes of the same name from WEB-INF,
+     * regardless of the value of {@link #setParentLoaderPriority(boolean)}.
+     * A class pattern is a string of one of the forms:<dl>
+     * <dt>org.package.Classname</dt><dd>Match a specific class</dd>
+     * <dt>org.package.</dt><dd>Match a specific package hierarchy</dd>
+     * <dt>-org.package.Classname</dt><dd>Exclude a specific class</dd>
+     * <dt>-org.package.</dt><dd>Exclude a specific package hierarchy</dd>
+     * </dl>
+     * @param systemClasses The systemClasses to set.
+     */
+    public void setSystemClasses(String[] systemClasses)
+    {
+        _systemClasses = new ClasspathPattern(systemClasses);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /** Set temporary directory for context.
+     * The javax.servlet.context.tempdir attribute is also set.
+     * @param dir Writable temporary directory.
+     */
+    public void setTempDirectory(File dir)
+    {
+        if (isStarted())
+            throw new IllegalStateException("Started");
+
+        if (dir!=null)
+        {
+            try{dir=new File(dir.getCanonicalPath());}
+            catch (IOException e){LOG.warn(Log.EXCEPTION,e);}
+        }
+
+        if (dir!=null && !dir.exists())
+        {
+            dir.mkdir();
+            dir.deleteOnExit();
+        }
+
+        if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
+            throw new IllegalArgumentException("Bad temp directory: "+dir);
+
+        try
+        {
+            if (dir!=null)
+                dir=dir.getCanonicalFile();
+        }
+        catch(Exception e)
+        {
+            LOG.warn(e);
+        }
+        _tmpDir=dir;
+        setAttribute(TEMPDIR,_tmpDir);
+    }
+
+    /* ------------------------------------------------------------ */
+    public File getTempDirectory ()
+    {
+        return _tmpDir;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param war The war to set as a file name or URL
+     */
+    public void setWar(String war)
+    {
+        _war = war;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return Comma or semicolon separated path of filenames or URLs
+     * pointing to directories or jar files. Directories should end
+     * with '/'.
+     */
+    public String getExtraClasspath()
+    {
+        return _extraClasspath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param extraClasspath Comma or semicolon separated path of filenames or URLs
+     * pointing to directories or jar files. Directories should end
+     * with '/'.
+     */
+    public void setExtraClasspath(String extraClasspath)
+    {
+        _extraClasspath=extraClasspath;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isLogUrlOnStart()
+    {
+        return _logUrlOnStart;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Sets whether or not the web app name and URL is logged on startup
+     *
+     * @param logOnStart whether or not the log message is created
+     */
+    public void setLogUrlOnStart(boolean logOnStart)
+    {
+        this._logUrlOnStart = logOnStart;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void setServer(Server server)
+    {
+        super.setServer(server);
+        //if we haven't been given a set of configuration instances to
+        //use, and we haven't been given a set of configuration classes
+        //to use, use the configuration classes that came from the
+        //Server (if there are any)
+        if (!_configurationsSet && !_configurationClassesSet && server != null)
+        {
+            String[] serverConfigs = (String[])server.getAttribute(SERVER_CONFIG);
+            if (serverConfigs != null)
+                setConfigurationClasses(serverConfigs);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isAllowDuplicateFragmentNames()
+    {
+        return _allowDuplicateFragmentNames;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames)
+    {
+        _allowDuplicateFragmentNames = allowDuplicateFragmentNames;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setThrowUnavailableOnStartupException (boolean throwIfStartupException) {
+        _throwUnavailableOnStartupException = throwIfStartupException;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public boolean isThrowUnavailableOnStartupException () {
+        return _throwUnavailableOnStartupException;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    protected void startContext()
+        throws Exception
+    {
+        configure();
+
+        //resolve the metadata
+        _metadata.resolve(this);
+
+        super.startContext();
+    }
+       
+    /* ------------------------------------------------------------ */    
+    @Override
+    public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
+    {
+     
+        Set<String> unchangedURLMappings = new HashSet<String>();
+        //From javadoc for ServletSecurityElement:
+        /*
+        If a URL pattern of this ServletRegistration is an exact target of a security-constraint that 
+        was established via the portable deployment descriptor, then this method does not change the 
+        security-constraint for that pattern, and the pattern will be included in the return value.
+
+        If a URL pattern of this ServletRegistration is an exact target of a security constraint 
+        that was established via the ServletSecurity annotation or a previous call to this method, 
+        then this method replaces the security constraint for that pattern.
+
+        If a URL pattern of this ServletRegistration is neither the exact target of a security constraint 
+        that was established via the ServletSecurity annotation or a previous call to this method, 
+        nor the exact target of a security-constraint in the portable deployment descriptor, then 
+        this method establishes the security constraint for that pattern from the argument ServletSecurityElement. 
+         */
+
+        Collection<String> pathMappings = registration.getMappings();
+        if (pathMappings != null)
+        {
+            Constraint constraint = ConstraintSecurityHandler.createConstraint(registration.getName(), servletSecurityElement);
+
+            for (String pathSpec:pathMappings)
+            {
+                Origin origin = getMetaData().getOrigin("constraint.url."+pathSpec);
+               
+                switch (origin)
+                {
+                    case NotSet:
+                    {
+                        //No mapping for this url already established
+                        List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+                        for (ConstraintMapping m:mappings)
+                            ((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
+                        getMetaData().setOrigin("constraint.url."+pathSpec, Origin.API);
+                        break;
+                    }
+                    case WebXml:
+                    case WebDefaults:
+                    case WebOverride:
+                    case WebFragment:
+                    {
+                        //a mapping for this url was created in a descriptor, which overrides everything
+                        unchangedURLMappings.add(pathSpec);
+                        break;
+                    }
+                    case Annotation:
+                    case API:
+                    {
+                        //mapping established via an annotation or by previous call to this method,
+                        //replace the security constraint for this pattern
+                        List<ConstraintMapping> constraintMappings = ConstraintSecurityHandler.removeConstraintMappingsForPath(pathSpec, ((ConstraintAware)getSecurityHandler()).getConstraintMappings());
+                       
+                        List<ConstraintMapping> freshMappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
+                        constraintMappings.addAll(freshMappings);
+                           
+                        ((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings);
+                        break;
+                    }
+                }
+            }
+        }
+        
+        return unchangedURLMappings;
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+    public class Context extends ServletContextHandler.Context
+    {
+        /* ------------------------------------------------------------ */
+        @Override
+        public URL getResource(String path) throws MalformedURLException
+        {
+            Resource resource=WebAppContext.this.getResource(path);
+            if (resource==null || !resource.exists())
+                return null;
+
+            // Should we go to the original war?
+            if (resource.isDirectory() && resource instanceof ResourceCollection && !WebAppContext.this.isExtractWAR())
+            {
+                Resource[] resources = ((ResourceCollection)resource).getResources();
+                for (int i=resources.length;i-->0;)
+                {
+                    if (resources[i].getName().startsWith("jar:file"))
+                        return resources[i].getURL();
+                }
+            }
+
+            return resource.getURL();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public ServletContext getContext(String uripath)
+        {
+            ServletContext servletContext = super.getContext(uripath);
+
+            if ( servletContext != null && _contextWhiteList != null )
+            {
+                for ( String context : _contextWhiteList )
+                {
+                    if ( context.equals(uripath) )
+                    {
+                        return servletContext;
+                    }
+                }
+
+                return null;
+            }
+            else
+            {
+                return servletContext;
+            }
+        }
+
+        
+        
+    }
+
+    /* ------------------------------------------------------------ */
+    public MetaData getMetaData()
+    {
+        return _metadata;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/webapp/WebDescriptor.java b/src/java/org/eclipse/jetty/webapp/WebDescriptor.java
new file mode 100644
index 0000000..19dba0a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/WebDescriptor.java
@@ -0,0 +1,276 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.Servlet;
+
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlParser;
+
+
+
+/**
+ * Descriptor
+ *
+ * A web descriptor (web.xml/web-defaults.xml/web-overrides.xml).
+ */
+public class WebDescriptor extends Descriptor
+{
+    private static final Logger LOG = Log.getLogger(WebDescriptor.class);
+ 
+    protected static XmlParser _parserSingleton;
+    protected MetaDataComplete _metaDataComplete;
+    protected int _majorVersion = 3; //default to container version
+    protected int _minorVersion = 0;
+    protected ArrayList<String> _classNames = new ArrayList<String>();
+    protected boolean _distributable;
+
+    protected boolean _isOrdered = false;
+    protected List<String> _ordering = new ArrayList<String>();
+    
+    @Override
+    public void ensureParser()
+    throws ClassNotFoundException
+    {
+        if (_parserSingleton == null)
+        {
+            _parserSingleton = newParser();
+        }
+        _parser = _parserSingleton;
+    }
+
+    
+    public XmlParser newParser()
+    throws ClassNotFoundException
+    {
+        XmlParser xmlParser=new XmlParser();
+        //set up cache of DTDs and schemas locally        
+        URL dtd22=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_2.dtd",true);
+        URL dtd23=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_3.dtd",true);
+        URL j2ee14xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_1_4.xsd",true);
+        URL webapp24xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_4.xsd",true);
+        URL webapp25xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_2_5.xsd",true);
+        URL webapp30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-app_3_0.xsd",true);
+        URL webcommon30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-common_3_0.xsd",true);
+        URL webfragment30xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/web-fragment_3_0.xsd",true);
+        URL schemadtd=Loader.getResource(Servlet.class,"javax/servlet/resources/XMLSchema.dtd",true);
+        URL xmlxsd=Loader.getResource(Servlet.class,"javax/servlet/resources/xml.xsd",true);
+        URL webservice11xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/j2ee_web_services_client_1_1.xsd",true);
+        URL webservice12xsd=Loader.getResource(Servlet.class,"javax/servlet/resources/javaee_web_services_client_1_2.xsd",true);
+        URL datatypesdtd=Loader.getResource(Servlet.class,"javax/servlet/resources/datatypes.dtd",true);
+
+        URL jsp20xsd = null;
+        URL jsp21xsd = null;
+
+        try
+        {
+            Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class, "javax.servlet.jsp.JspPage");
+            jsp20xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_0.xsd");
+            jsp21xsd = jsp_page.getResource("/javax/servlet/resources/jsp_2_1.xsd");
+        }
+        catch (Exception e)
+        {
+            LOG.ignore(e);
+        }
+        finally
+        {
+            if (jsp20xsd == null) jsp20xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_0.xsd", true);
+            if (jsp21xsd == null) jsp21xsd = Loader.getResource(Servlet.class, "javax/servlet/resources/jsp_2_1.xsd", true);
+        }
+        
+        redirect(xmlParser,"web-app_2_2.dtd",dtd22);
+        redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",dtd22);
+        redirect(xmlParser,"web.dtd",dtd23);
+        redirect(xmlParser,"web-app_2_3.dtd",dtd23);
+        redirect(xmlParser,"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",dtd23);
+        redirect(xmlParser,"XMLSchema.dtd",schemadtd);
+        redirect(xmlParser,"http://www.w3.org/2001/XMLSchema.dtd",schemadtd);
+        redirect(xmlParser,"-//W3C//DTD XMLSCHEMA 200102//EN",schemadtd);
+        redirect(xmlParser,"jsp_2_0.xsd",jsp20xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",jsp20xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/jsp_2_1.xsd",jsp21xsd);
+        redirect(xmlParser,"j2ee_1_4.xsd",j2ee14xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",j2ee14xsd);
+        redirect(xmlParser,"web-app_2_4.xsd",webapp24xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",webapp24xsd);
+        redirect(xmlParser,"web-app_2_5.xsd",webapp25xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd",webapp25xsd);
+        redirect(xmlParser,"web-app_3_0.xsd",webapp30xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd",webapp30xsd);
+        redirect(xmlParser,"web-common_3_0.xsd",webcommon30xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-common_3_0.xsd",webcommon30xsd);
+        redirect(xmlParser,"web-fragment_3_0.xsd",webfragment30xsd);
+        redirect(xmlParser,"http://java.sun.com/xml/ns/javaee/web-fragment_3_0.xsd",webfragment30xsd);
+        redirect(xmlParser,"xml.xsd",xmlxsd);
+        redirect(xmlParser,"http://www.w3.org/2001/xml.xsd",xmlxsd);
+        redirect(xmlParser,"datatypes.dtd",datatypesdtd);
+        redirect(xmlParser,"http://www.w3.org/2001/datatypes.dtd",datatypesdtd);
+        redirect(xmlParser,"j2ee_web_services_client_1_1.xsd",webservice11xsd);
+        redirect(xmlParser,"http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",webservice11xsd);
+        redirect(xmlParser,"javaee_web_services_client_1_2.xsd",webservice12xsd);
+        redirect(xmlParser,"http://www.ibm.com/webservices/xsd/javaee_web_services_client_1_2.xsd",webservice12xsd);
+        return xmlParser;
+    }
+    
+    
+    public WebDescriptor (Resource xml)
+    {
+        super(xml);
+    }
+    
+    public void parse ()
+    throws Exception
+    {
+        super.parse();
+        processVersion();
+        processOrdering();
+    }
+    
+    public MetaDataComplete getMetaDataComplete()
+    {
+        return _metaDataComplete;
+    }
+    
+ 
+    
+    public int getMajorVersion ()
+    {
+        return _majorVersion;
+    }
+    
+    public int getMinorVersion()
+    {
+        return _minorVersion;
+    }
+  
+    
+    public void processVersion ()
+    {
+        String version = _root.getAttribute("version", "DTD");
+        if ("DTD".equals(version))
+        {
+            _majorVersion = 2;
+            _minorVersion = 3;
+            String dtd = _parser.getDTD();
+            if (dtd != null && dtd.indexOf("web-app_2_2") >= 0)
+            {
+                _majorVersion = 2;
+                _minorVersion = 2;
+            }
+        }
+        else 
+        {
+           int dot = version.indexOf(".");
+           if (dot > 0)
+           {
+               _majorVersion = Integer.parseInt(version.substring(0,dot));
+               _minorVersion = Integer.parseInt(version.substring(dot+1));
+           }
+        }
+     
+        if (_majorVersion < 2 && _minorVersion < 5)
+            _metaDataComplete = MetaDataComplete.True; // does not apply before 2.5
+        else
+        {
+            String s = (String)_root.getAttribute("metadata-complete");
+            if (s == null)
+                _metaDataComplete = MetaDataComplete.NotSet;
+            else
+                _metaDataComplete = Boolean.valueOf(s).booleanValue()?MetaDataComplete.True:MetaDataComplete.False;
+        }
+            
+        if (LOG.isDebugEnabled())
+            LOG.debug(_xml.toString()+": Calculated metadatacomplete = " + _metaDataComplete + " with version=" + version);     
+    }
+    
+    public void processOrdering ()
+    {
+        //Process the web.xml's optional <absolute-ordering> element              
+        XmlParser.Node ordering = _root.get("absolute-ordering");
+        if (ordering == null)
+           return;
+        
+        _isOrdered = true;
+        //If an absolute-ordering was already set, then ignore it in favor of this new one
+       // _processor.setOrdering(new AbsoluteOrdering());
+   
+        Iterator<Object> iter = ordering.iterator();
+        XmlParser.Node node = null;
+        while (iter.hasNext())
+        {
+            Object o = iter.next();
+            if (!(o instanceof XmlParser.Node)) continue;
+            node = (XmlParser.Node) o;
+
+            if (node.getTag().equalsIgnoreCase("others"))
+                //((AbsoluteOrdering)_processor.getOrdering()).addOthers();
+                _ordering.add("others");
+            else if (node.getTag().equalsIgnoreCase("name"))
+                //((AbsoluteOrdering)_processor.getOrdering()).add(node.toString(false,true));
+                _ordering.add(node.toString(false,true));
+        }
+    }
+  
+    public void addClassName (String className)
+    {
+        if (!_classNames.contains(className))
+            _classNames.add(className);
+    }
+    
+    public ArrayList<String> getClassNames ()
+    {
+        return _classNames;
+    }
+    
+    public void setDistributable (boolean distributable)
+    {
+        _distributable = distributable;
+    }
+    
+    public boolean isDistributable()
+    {
+        return _distributable;
+    }
+    
+    public void setValidating (boolean validating)
+    {
+       _validating = validating;
+    }
+    
+    
+    public boolean isOrdered()
+    {
+        return _isOrdered;
+    }
+    
+    public List<String> getOrdering()
+    {
+        return _ordering;
+    }
+
+  
+}
diff --git a/src/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/src/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
new file mode 100644
index 0000000..0695591
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
@@ -0,0 +1,732 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.PatternMatcher;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.JarResource;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+
+public class WebInfConfiguration extends AbstractConfiguration
+{
+    private static final Logger LOG = Log.getLogger(WebInfConfiguration.class);
+
+    public static final String TEMPDIR_CONFIGURED = "org.eclipse.jetty.tmpdirConfigured";
+    public static final String CONTAINER_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";
+    public static final String WEBINF_JAR_PATTERN = "org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern";
+    
+    /**
+     * If set, to a list of URLs, these resources are added to the context
+     * resource base as a resource collection. 
+     */
+    public static final String RESOURCE_URLS = "org.eclipse.jetty.resources";
+    
+    protected Resource _preUnpackBaseResource;
+    
+    @Override
+    public void preConfigure(final WebAppContext context) throws Exception
+    {
+        // Look for a work directory
+        File work = findWorkDirectory(context);
+        if (work != null)
+            makeTempDirectory(work, context, false);
+        
+        //Make a temp directory for the webapp if one is not already set
+        resolveTempDirectory(context);
+        
+        //Extract webapp if necessary
+        unpack (context);
+
+        
+        //Apply an initial ordering to the jars which governs which will be scanned for META-INF
+        //info and annotations. The ordering is based on inclusion patterns.       
+        String tmp = (String)context.getAttribute(WEBINF_JAR_PATTERN);
+        Pattern webInfPattern = (tmp==null?null:Pattern.compile(tmp));
+        tmp = (String)context.getAttribute(CONTAINER_JAR_PATTERN);
+        Pattern containerPattern = (tmp==null?null:Pattern.compile(tmp));
+
+        //Apply ordering to container jars - if no pattern is specified, we won't
+        //match any of the container jars
+        PatternMatcher containerJarNameMatcher = new PatternMatcher ()
+        {
+            public void matched(URI uri) throws Exception
+            {
+                context.getMetaData().addContainerJar(Resource.newResource(uri));
+            }      
+        };
+        ClassLoader loader = null;
+        if (context.getClassLoader() != null)
+            loader = context.getClassLoader().getParent();
+
+        while (loader != null && (loader instanceof URLClassLoader))
+        {
+            URL[] urls = ((URLClassLoader)loader).getURLs();
+            if (urls != null)
+            {
+                URI[] containerUris = new URI[urls.length];
+                int i=0;
+                for (URL u : urls)
+                {
+                    try 
+                    {
+                        containerUris[i] = u.toURI();
+                    }
+                    catch (URISyntaxException e)
+                    {
+                        containerUris[i] = new URI(u.toString().replaceAll(" ", "%20"));
+                    }  
+                    i++;
+                }
+                containerJarNameMatcher.match(containerPattern, containerUris, false);
+            }
+            loader = loader.getParent();
+        }
+        
+        //Apply ordering to WEB-INF/lib jars
+        PatternMatcher webInfJarNameMatcher = new PatternMatcher ()
+        {
+            @Override
+            public void matched(URI uri) throws Exception
+            {
+                context.getMetaData().addWebInfJar(Resource.newResource(uri));
+            }      
+        };
+        List<Resource> jars = findJars(context);
+       
+        //Convert to uris for matching
+        URI[] uris = null;
+        if (jars != null)
+        {
+            uris = new URI[jars.size()];
+            int i=0;
+            for (Resource r: jars)
+            {
+                uris[i++] = r.getURI();
+            }
+        }
+        webInfJarNameMatcher.match(webInfPattern, uris, true); //null is inclusive, no pattern == all jars match 
+    }
+    
+
+    @Override
+    public void configure(WebAppContext context) throws Exception
+    {
+        //cannot configure if the context is already started
+        if (context.isStarted())
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("Cannot configure webapp "+context+" after it is started");
+            return;
+        }
+
+        Resource web_inf = context.getWebInf();
+
+        // Add WEB-INF classes and lib classpaths
+        if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
+        {
+            // Look for classes directory
+            Resource classes= web_inf.addPath("classes/");
+            if (classes.exists())
+                ((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
+
+            // Look for jars
+            Resource lib= web_inf.addPath("lib/");
+            if (lib.exists() || lib.isDirectory())
+                ((WebAppClassLoader)context.getClassLoader()).addJars(lib);
+        }
+        
+        // Look for extra resource
+        @SuppressWarnings("unchecked")
+        List<Resource> resources = (List<Resource>)context.getAttribute(RESOURCE_URLS);
+        if (resources!=null)
+        {
+            Resource[] collection=new Resource[resources.size()+1];
+            int i=0;
+            collection[i++]=context.getBaseResource();
+            for (Resource resource : resources)
+                collection[i++]=resource;
+            context.setBaseResource(new ResourceCollection(collection));
+        }
+    }
+
+    @Override
+    public void deconfigure(WebAppContext context) throws Exception
+    {
+        // delete temp directory if we had to create it or if it isn't called work
+        Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED);
+        
+        if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory()))
+        {
+            IO.delete(context.getTempDirectory());
+            context.setTempDirectory(null);
+            
+            //clear out the context attributes for the tmp dir only if we had to
+            //create the tmp dir
+            context.setAttribute(TEMPDIR_CONFIGURED, null);
+            context.setAttribute(WebAppContext.TEMPDIR, null);
+        }
+
+        
+        //reset the base resource back to what it was before we did any unpacking of resources
+        context.setBaseResource(_preUnpackBaseResource);
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
+     */
+    @Override
+    public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
+    {
+        File tmpDir=File.createTempFile(WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context),"",template.getTempDirectory().getParentFile());
+        if (tmpDir.exists())
+        {
+            IO.delete(tmpDir);
+        }
+        tmpDir.mkdir();
+        tmpDir.deleteOnExit();
+        context.setTempDirectory(tmpDir);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get a temporary directory in which to unpack the war etc etc.
+     * The algorithm for determining this is to check these alternatives
+     * in the order shown:
+     * 
+     * <p>A. Try to use an explicit directory specifically for this webapp:</p>
+     * <ol>
+     * <li>
+     * Iff an explicit directory is set for this webapp, use it. Do NOT set
+     * delete on exit.
+     * </li>
+     * <li>
+     * Iff javax.servlet.context.tempdir context attribute is set for
+     * this webapp && exists && writeable, then use it. Do NOT set delete on exit.
+     * </li>
+     * </ol>
+     * 
+     * <p>B. Create a directory based on global settings. The new directory 
+     * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost
+     * Work out where to create this directory:
+     * <ol>
+     * <li>
+     * Iff $(jetty.home)/work exists create the directory there. Do NOT
+     * set delete on exit. Do NOT delete contents if dir already exists.
+     * </li>
+     * <li>
+     * Iff WEB-INF/work exists create the directory there. Do NOT set
+     * delete on exit. Do NOT delete contents if dir already exists.
+     * </li>
+     * <li>
+     * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete
+     * contents if dir already exists.
+     * </li>
+     * </ol>
+     */
+    public void resolveTempDirectory (WebAppContext context)
+    {
+        //If a tmp directory is already set, we're done
+        File tmpDir = context.getTempDirectory();
+        if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite())
+        {
+            context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE);
+            return; // Already have a suitable tmp dir configured
+        }
+        
+
+        // No temp directory configured, try to establish one.
+        // First we check the context specific, javax.servlet specified, temp directory attribute
+        File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR));
+        if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite())
+        {
+            // Use as tmpDir
+            tmpDir = servletTmpDir;
+            // Ensure Attribute has File object
+            context.setAttribute(WebAppContext.TEMPDIR,tmpDir);
+            // Set as TempDir in context.
+            context.setTempDirectory(tmpDir);
+            return;
+        }
+
+        try
+        {
+            // Put the tmp dir in the work directory if we had one
+            File work =  new File(System.getProperty("jetty.home"),"work");
+            if (work.exists() && work.canWrite() && work.isDirectory())
+            {
+                makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists
+            }
+            else
+            {
+                File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR));
+                if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite())
+                {
+                    // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in
+                    makeTempDirectory(baseTemp,context,false);
+                }
+                else
+                {
+                    makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists
+                }
+            }
+        }
+        catch(Exception e)
+        {
+            tmpDir=null;
+            LOG.ignore(e);
+        }
+
+        //Third ... Something went wrong trying to make the tmp directory, just make
+        //a jvm managed tmp directory
+        if (context.getTempDirectory() == null)
+        {
+            try
+            {
+                // Last resort
+                tmpDir=File.createTempFile("JettyContext","");
+                if (tmpDir.exists())
+                    IO.delete(tmpDir);
+                tmpDir.mkdir();
+                tmpDir.deleteOnExit();
+                context.setTempDirectory(tmpDir);
+            }
+            catch(IOException e)
+            {
+                tmpDir = null;
+                throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e);
+            }
+        }
+    }
+    
+    /**
+     * Given an Object, return File reference for object.
+     * Typically used to convert anonymous Object from getAttribute() calls to a File object.
+     * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null)
+     * @return the File object, null if null, or null if not a File or String
+     */
+    private File asFile(Object fileattr)
+    {
+        if (fileattr == null)
+        {
+            return null;
+        }
+        if (fileattr instanceof File)
+        {
+            return (File)fileattr;
+        }
+        if (fileattr instanceof String)
+        {
+            return new File((String)fileattr);
+        }
+        return null;
+    }
+
+
+
+    public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting)
+    throws IOException
+    {
+        if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory())
+        {
+            String temp = getCanonicalNameForWebAppTmpDir(context);                    
+            File tmpDir = new File(parent,temp);
+
+            if (deleteExisting && tmpDir.exists())
+            {
+                if (!IO.delete(tmpDir))
+                {
+                    if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir);
+                }
+            
+                //If we can't delete the existing tmp dir, create a new one
+                if (tmpDir.exists())
+                {
+                    String old=tmpDir.toString();
+                    tmpDir=File.createTempFile(temp+"_","");
+                    if (tmpDir.exists())
+                        IO.delete(tmpDir);
+                    LOG.warn("Can't reuse "+old+", using "+tmpDir);
+                } 
+            }
+            
+            if (!tmpDir.exists())
+                tmpDir.mkdir();
+
+            //If the parent is not a work directory
+            if (!isTempWorkDirectory(tmpDir))
+            {
+                tmpDir.deleteOnExit();
+            }
+
+            if(LOG.isDebugEnabled())
+                LOG.debug("Set temp dir "+tmpDir);
+            context.setTempDirectory(tmpDir);
+        }
+    }
+    
+    
+    public void unpack (WebAppContext context) throws IOException
+    {
+        Resource web_app = context.getBaseResource();
+        _preUnpackBaseResource = context.getBaseResource();
+        
+        if (web_app == null)
+        {
+            String war = context.getWar();
+            if (war!=null && war.length()>0)
+                web_app = context.newResource(war);
+            else
+                web_app=context.getBaseResource();
+
+            // Accept aliases for WAR files
+            if (web_app.getAlias() != null)
+            {
+                LOG.debug(web_app + " anti-aliased to " + web_app.getAlias());
+                web_app = context.newResource(web_app.getAlias());
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("Try webapp=" + web_app + ", exists=" + web_app.exists() + ", directory=" + web_app.isDirectory()+" file="+(web_app.getFile()));
+            // Is the WAR usable directly?
+            if (web_app.exists() && !web_app.isDirectory() && !web_app.toString().startsWith("jar:"))
+            {
+                // No - then lets see if it can be turned into a jar URL.
+                Resource jarWebApp = JarResource.newJarResource(web_app);
+                if (jarWebApp.exists() && jarWebApp.isDirectory())
+                    web_app= jarWebApp;
+            }
+
+            // If we should extract or the URL is still not usable
+            if (web_app.exists()  && (
+                    (context.isCopyWebDir() && web_app.getFile() != null && web_app.getFile().isDirectory()) ||
+                    (context.isExtractWAR() && web_app.getFile() != null && !web_app.getFile().isDirectory()) ||
+                    (context.isExtractWAR() && web_app.getFile() == null) || 
+                    !web_app.isDirectory())
+                            )
+            {
+                // Look for sibling directory.
+                File extractedWebAppDir = null;
+
+                if (war!=null)
+                {
+                    // look for a sibling like "foo/" to a "foo.war"
+                    File warfile=Resource.newResource(war).getFile();
+                    if (warfile!=null && warfile.getName().toLowerCase(Locale.ENGLISH).endsWith(".war"))
+                    {
+                        File sibling = new File(warfile.getParent(),warfile.getName().substring(0,warfile.getName().length()-4));
+                        if (sibling.exists() && sibling.isDirectory() && sibling.canWrite())
+                            extractedWebAppDir=sibling;
+                    }
+                }
+                
+                if (extractedWebAppDir==null)
+                    // Then extract it if necessary to the temporary location
+                    extractedWebAppDir= new File(context.getTempDirectory(), "webapp");
+
+                if (web_app.getFile()!=null && web_app.getFile().isDirectory())
+                {
+                    // Copy directory
+                    LOG.info("Copy " + web_app + " to " + extractedWebAppDir);
+                    web_app.copyTo(extractedWebAppDir);
+                }
+                else
+                {
+                    //Use a sentinel file that will exist only whilst the extraction is taking place.
+                    //This will help us detect interrupted extractions.
+                    File extractionLock = new File (context.getTempDirectory(), ".extract_lock");
+                   
+                    if (!extractedWebAppDir.exists())
+                    {
+                        //it hasn't been extracted before so extract it
+                        extractionLock.createNewFile();  
+                        extractedWebAppDir.mkdir();
+                        LOG.info("Extract " + web_app + " to " + extractedWebAppDir);                                     
+                        Resource jar_web_app = JarResource.newJarResource(web_app);
+                        jar_web_app.copyTo(extractedWebAppDir);
+                        extractionLock.delete();
+                    }
+                    else
+                    {
+                        //only extract if the war file is newer, or a .extract_lock file is left behind meaning a possible partial extraction
+                        if (web_app.lastModified() > extractedWebAppDir.lastModified() || extractionLock.exists())
+                        {
+                            extractionLock.createNewFile();
+                            IO.delete(extractedWebAppDir);
+                            extractedWebAppDir.mkdir();
+                            LOG.info("Extract " + web_app + " to " + extractedWebAppDir);
+                            Resource jar_web_app = JarResource.newJarResource(web_app);
+                            jar_web_app.copyTo(extractedWebAppDir);
+                            extractionLock.delete();
+                        }
+                    }
+                } 
+                web_app = Resource.newResource(extractedWebAppDir.getCanonicalPath());
+            }
+
+            // Now do we have something usable?
+            if (!web_app.exists() || !web_app.isDirectory())
+            {
+                LOG.warn("Web application not found " + war);
+                throw new java.io.FileNotFoundException(war);
+            }
+        
+            context.setBaseResource(web_app);
+            
+            if (LOG.isDebugEnabled())
+                LOG.debug("webapp=" + web_app);
+        }
+        
+
+        // Do we need to extract WEB-INF/lib?
+        if (context.isCopyWebInf() && !context.isCopyWebDir())
+        {
+            Resource web_inf= web_app.addPath("WEB-INF/");
+
+            File extractedWebInfDir= new File(context.getTempDirectory(), "webinf");
+            if (extractedWebInfDir.exists())
+                IO.delete(extractedWebInfDir);
+            extractedWebInfDir.mkdir();
+            Resource web_inf_lib = web_inf.addPath("lib/");
+            File webInfDir=new File(extractedWebInfDir,"WEB-INF");
+            webInfDir.mkdir();
+
+            if (web_inf_lib.exists())
+            {
+                File webInfLibDir = new File(webInfDir, "lib");
+                if (webInfLibDir.exists())
+                    IO.delete(webInfLibDir);
+                webInfLibDir.mkdir();
+
+                LOG.info("Copying WEB-INF/lib " + web_inf_lib + " to " + webInfLibDir);
+                web_inf_lib.copyTo(webInfLibDir);
+            }
+
+            Resource web_inf_classes = web_inf.addPath("classes/");
+            if (web_inf_classes.exists())
+            {
+                File webInfClassesDir = new File(webInfDir, "classes");
+                if (webInfClassesDir.exists())
+                    IO.delete(webInfClassesDir);
+                webInfClassesDir.mkdir();
+                LOG.info("Copying WEB-INF/classes from "+web_inf_classes+" to "+webInfClassesDir.getAbsolutePath());
+                web_inf_classes.copyTo(webInfClassesDir);
+            }
+
+            web_inf=Resource.newResource(extractedWebInfDir.getCanonicalPath());
+
+            ResourceCollection rc = new ResourceCollection(web_inf,web_app);
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("context.resourcebase = "+rc);
+
+            context.setBaseResource(rc);   
+        }
+    }
+    
+    
+    public File findWorkDirectory (WebAppContext context) throws IOException
+    {
+        if (context.getBaseResource() != null)
+        {
+            Resource web_inf = context.getWebInf();
+            if (web_inf !=null && web_inf.exists())
+            {
+               return new File(web_inf.getFile(),"work");
+            }
+        }
+        return null;
+    }
+    
+    
+    /**
+     * Check if the tmpDir itself is called "work", or if the tmpDir
+     * is in a directory called "work".
+     * @return true if File is a temporary or work directory
+     */
+    public boolean isTempWorkDirectory (File tmpDir)
+    {
+        if (tmpDir == null)
+            return false;
+        if (tmpDir.getName().equalsIgnoreCase("work"))
+            return true;
+        File t = tmpDir.getParentFile();
+        if (t == null)
+            return false;
+        return (t.getName().equalsIgnoreCase("work"));
+    }
+    
+    
+    /**
+     * Create a canonical name for a webapp temp directory.
+     * The form of the name is:
+     *  <code>"Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string</code>
+     *  
+     *  host and port uniquely identify the server
+     *  context and virtual host uniquely identify the webapp
+     * @return the canonical name for the webapp temp directory
+     */
+    public static String getCanonicalNameForWebAppTmpDir (WebAppContext context)
+    {
+        StringBuffer canonicalName = new StringBuffer();
+        canonicalName.append("jetty-");
+       
+        //get the host and the port from the first connector 
+        Server server=context.getServer();
+        if (server!=null)
+        {
+            Connector[] connectors = context.getServer().getConnectors();
+
+            if (connectors.length>0)
+            {
+                //Get the host
+                String host = (connectors==null||connectors[0]==null?"":connectors[0].getHost());
+                if (host == null)
+                    host = "0.0.0.0";
+                canonicalName.append(host);
+                
+                //Get the port
+                canonicalName.append("-");
+                //try getting the real port being listened on
+                int port = (connectors==null||connectors[0]==null?0:connectors[0].getLocalPort());
+                //if not available (eg no connectors or connector not started), 
+                //try getting one that was configured.
+                if (port < 0)
+                    port = connectors[0].getPort();
+                canonicalName.append(port);
+                canonicalName.append("-");
+            }
+        }
+
+       
+        //Resource  base
+        try
+        {
+            Resource resource = context.getBaseResource();
+            if (resource == null)
+            {
+                if (context.getWar()==null || context.getWar().length()==0)
+                    resource=context.newResource(context.getResourceBase());
+                
+                // Set dir or WAR
+                resource = context.newResource(context.getWar());
+            }
+                
+            String tmp = URIUtil.decodePath(resource.getURL().getPath());
+            if (tmp.endsWith("/"))
+                tmp = tmp.substring(0, tmp.length()-1);
+            if (tmp.endsWith("!"))
+                tmp = tmp.substring(0, tmp.length() -1);
+            //get just the last part which is the filename
+            int i = tmp.lastIndexOf("/");
+            canonicalName.append(tmp.substring(i+1, tmp.length()));
+            canonicalName.append("-");
+        }
+        catch (Exception e)
+        {
+            LOG.warn("Can't generate resourceBase as part of webapp tmp dir name", e);
+        }
+            
+        //Context name
+        String contextPath = context.getContextPath();
+        contextPath=contextPath.replace('/','_');
+        contextPath=contextPath.replace('\\','_');
+        canonicalName.append(contextPath);
+        
+        //Virtual host (if there is one)
+        canonicalName.append("-");
+        String[] vhosts = context.getVirtualHosts();
+        if (vhosts == null || vhosts.length <= 0)
+            canonicalName.append("any");
+        else
+            canonicalName.append(vhosts[0]);
+        
+        // sanitize
+        for (int i=0;i<canonicalName.length();i++)
+        {
+            char c=canonicalName.charAt(i);
+            if (!Character.isJavaIdentifierPart(c) && "-.".indexOf(c)<0)
+                canonicalName.setCharAt(i,'.');
+        }        
+
+        canonicalName.append("-");
+        return canonicalName.toString();
+    }
+    
+    /**
+     * Look for jars in WEB-INF/lib
+     * @param context
+     * @return the list of jar resources found within context 
+     * @throws Exception
+     */
+    protected List<Resource> findJars (WebAppContext context) 
+    throws Exception
+    {
+        List<Resource> jarResources = new ArrayList<Resource>();
+        
+        Resource web_inf = context.getWebInf();
+        if (web_inf==null || !web_inf.exists())
+            return null;
+        
+        Resource web_inf_lib = web_inf.addPath("/lib");
+       
+        
+        if (web_inf_lib.exists() && web_inf_lib.isDirectory())
+        {
+            String[] files=web_inf_lib.list();
+            for (int f=0;files!=null && f<files.length;f++)
+            {
+                try 
+                {
+                    Resource file = web_inf_lib.addPath(files[f]);
+                    String fnlc = file.getName().toLowerCase(Locale.ENGLISH);
+                    int dot = fnlc.lastIndexOf('.');
+                    String extension = (dot < 0 ? null : fnlc.substring(dot));
+                    if (extension != null && (extension.equals(".jar") || extension.equals(".zip")))
+                    {
+                        jarResources.add(file);
+                    }
+                }
+                catch (Exception ex)
+                {
+                    LOG.warn(Log.EXCEPTION,ex);
+                }
+            }
+        }
+        return jarResources;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java b/src/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
new file mode 100644
index 0000000..0211cea
--- /dev/null
+++ b/src/java/org/eclipse/jetty/webapp/WebXmlConfiguration.java
@@ -0,0 +1,136 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.webapp;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+
+/* ------------------------------------------------------------------------------- */
+/**
+ * Configure by parsing default web.xml and web.xml
+ * 
+ */
+public class WebXmlConfiguration extends AbstractConfiguration
+{
+    private static final Logger LOG = Log.getLogger(WebXmlConfiguration.class);
+
+    
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * 
+     */
+    @Override
+    public void preConfigure (WebAppContext context) throws Exception
+    {
+        //parse webdefault.xml
+        String defaultsDescriptor = context.getDefaultsDescriptor();
+        if (defaultsDescriptor != null && defaultsDescriptor.length() > 0)
+        {
+            Resource dftResource = Resource.newSystemResource(defaultsDescriptor);
+            if (dftResource == null) 
+                dftResource = context.newResource(defaultsDescriptor);
+            context.getMetaData().setDefaults (dftResource);
+        }
+        
+        //parse, but don't process web.xml
+        Resource webxml = findWebXml(context);
+        if (webxml != null) 
+        {      
+            context.getMetaData().setWebXml(webxml);
+            context.getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebXml().getMajorVersion());
+            context.getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebXml().getMinorVersion());
+        }
+        
+        //parse but don't process override-web.xml
+        for (String overrideDescriptor : context.getOverrideDescriptors())
+        {
+            if (overrideDescriptor != null && overrideDescriptor.length() > 0)
+            {
+                Resource orideResource = Resource.newSystemResource(overrideDescriptor);
+                if (orideResource == null) 
+                    orideResource = context.newResource(overrideDescriptor);
+                context.getMetaData().addOverride(orideResource);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------------------------- */
+    /**
+     * Process web-default.xml, web.xml, override-web.xml
+     * 
+     */
+    @Override
+    public void configure (WebAppContext context) throws Exception
+    {
+        // cannot configure if the context is already started
+        if (context.isStarted())
+        {
+            LOG.debug("Cannot configure webapp after it is started");
+            return;
+        }
+
+        context.getMetaData().addDescriptorProcessor(new StandardDescriptorProcessor());
+    }
+    
+    /* ------------------------------------------------------------------------------- */
+    protected Resource findWebXml(WebAppContext context) throws IOException, MalformedURLException
+    {
+        String descriptor = context.getDescriptor();
+        if (descriptor != null)
+        {
+            Resource web = context.newResource(descriptor);
+            if (web.exists() && !web.isDirectory()) return web;
+        }
+
+        Resource web_inf = context.getWebInf();
+        if (web_inf != null && web_inf.isDirectory())
+        {
+            // do web.xml file
+            Resource web = web_inf.addPath("web.xml");
+            if (web.exists()) return web;
+            if (LOG.isDebugEnabled())
+                LOG.debug("No WEB-INF/web.xml in " + context.getWar() + ". Serving files and default/dynamic servlets only");
+        }
+        return null;
+    }
+
+
+    /* ------------------------------------------------------------------------------- */
+    @Override
+    public void deconfigure (WebAppContext context) throws Exception
+    {
+        ServletHandler _servletHandler = context.getServletHandler();
+       
+        context.setWelcomeFiles(null);
+
+        if (context.getErrorHandler() instanceof ErrorPageErrorHandler)
+            ((ErrorPageErrorHandler) 
+                    context.getErrorHandler()).setErrorPages(null);
+
+
+        // TODO remove classpaths from classloader
+
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/AbstractExtension.java b/src/java/org/eclipse/jetty/websocket/AbstractExtension.java
new file mode 100644
index 0000000..382ea6d
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/AbstractExtension.java
@@ -0,0 +1,149 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
+
+public class AbstractExtension implements Extension
+{
+    private static final int[] __mask = { -1, 0x04, 0x02, 0x01};
+    private final String _name;
+    private final Map<String,String> _parameters=new HashMap<String, String>();
+    private FrameHandler _inbound;
+    private WebSocketGenerator _outbound;
+    private WebSocket.FrameConnection _connection;
+    
+    public AbstractExtension(String name)
+    {
+        _name = name;
+    }
+    
+    public WebSocket.FrameConnection getConnection()
+    {
+        return _connection;
+    }
+
+    public boolean init(Map<String, String> parameters)
+    {
+        _parameters.putAll(parameters);
+        return true;
+    }
+    
+    public String getInitParameter(String name)
+    {
+        return _parameters.get(name);
+    }
+
+    public String getInitParameter(String name,String dft)
+    {
+        if (!_parameters.containsKey(name))
+            return dft;
+        return _parameters.get(name);
+    }
+
+    public int getInitParameter(String name, int dft)
+    {
+        String v=_parameters.get(name);
+        if (v==null)
+            return dft;
+        return Integer.valueOf(v);
+    }
+    
+    
+    public void bind(WebSocket.FrameConnection connection, FrameHandler incoming, WebSocketGenerator outgoing)
+    {
+        _connection=connection;
+        _inbound=incoming;
+        _outbound=outgoing;
+    }
+
+    public String getName()
+    {
+        return _name;
+    }
+
+    public String getParameterizedName()
+    {
+        StringBuilder name = new StringBuilder();
+        name.append(_name);
+        for (String param : _parameters.keySet())
+            name.append(';').append(param).append('=').append(QuotedStringTokenizer.quoteIfNeeded(_parameters.get(param),";="));
+        return name.toString();
+    }
+
+    public void onFrame(byte flags, byte opcode, Buffer buffer)
+    {
+        // System.err.printf("onFrame %s %x %x %d\n",getExtensionName(),flags,opcode,buffer.length());
+        _inbound.onFrame(flags,opcode,buffer);
+    }
+
+    public void close(int code, String message)
+    {
+        _inbound.close(code,message);
+    }
+
+    public int flush() throws IOException
+    {
+        return _outbound.flush();
+    }
+
+    public boolean isBufferEmpty()
+    {
+        return _outbound.isBufferEmpty();
+    }
+
+    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        // System.err.printf("addFrame %s %x %x %d\n",getExtensionName(),flags,opcode,length);
+        _outbound.addFrame(flags,opcode,content,offset,length);
+    }
+    
+    public byte setFlag(byte flags,int rsv)
+    {
+        if (rsv<1||rsv>3)
+            throw new IllegalArgumentException("rsv"+rsv);
+        byte b=(byte)(flags | __mask[rsv]);
+        return b;
+    }
+    
+    public byte clearFlag(byte flags,int rsv)
+    {
+        if (rsv<1||rsv>3)
+            throw new IllegalArgumentException("rsv"+rsv);
+        return (byte)(flags & ~__mask[rsv]);
+    }
+
+    public boolean isFlag(byte flags,int rsv)
+    {
+        if (rsv<1||rsv>3)
+            throw new IllegalArgumentException("rsv"+rsv);
+        return (flags & __mask[rsv])!=0;
+    }
+    
+    public String toString()
+    {
+        return getParameterizedName();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java b/src/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java
new file mode 100644
index 0000000..6647622
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/DeflateFrameExtension.java
@@ -0,0 +1,164 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * TODO Implement proposed deflate frame draft
+ */
+public class DeflateFrameExtension extends AbstractExtension
+{
+    private static final Logger LOG = Log.getLogger(DeflateFrameExtension.class);
+
+    private int _minLength=8;
+    private Deflater _deflater;
+    private Inflater _inflater;
+
+    public DeflateFrameExtension()
+    {
+        super("x-deflate-frame");
+    }
+
+    @Override
+    public boolean init(Map<String, String> parameters)
+    {
+        if (!parameters.containsKey("minLength"))
+            parameters.put("minLength",Integer.toString(_minLength));
+        if(super.init(parameters))
+        {
+            _minLength=getInitParameter("minLength",_minLength);
+
+            _deflater=new Deflater();
+            _inflater=new Inflater();
+
+            return true;
+        }
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.websocket.AbstractExtension#onFrame(byte, byte, org.eclipse.jetty.io.Buffer)
+     */
+    @Override
+    public void onFrame(byte flags, byte opcode, Buffer buffer)
+    {
+        if (getConnection().isControl(opcode) || !isFlag(flags,1))
+        {
+            super.onFrame(flags,opcode,buffer);
+            return;
+        }
+
+        if (buffer.array()==null)
+            buffer=buffer.asMutableBuffer();
+
+        int length=0xff&buffer.get();
+        if (length>=0x7e)
+        {
+            int b=(length==0x7f)?8:2;
+            length=0;
+            while(b-->0)
+                length=0x100*length+(0xff&buffer.get());
+        }
+
+        // TODO check a max framesize
+
+        _inflater.setInput(buffer.array(),buffer.getIndex(),buffer.length());
+        ByteArrayBuffer buf = new ByteArrayBuffer(length);
+        try
+        {
+            while(_inflater.getRemaining()>0)
+            {
+                int inflated=_inflater.inflate(buf.array(),buf.putIndex(),buf.space());
+                if (inflated==0)
+                    throw new DataFormatException("insufficient data");
+                buf.setPutIndex(buf.putIndex()+inflated);
+            }
+
+            super.onFrame(clearFlag(flags,1),opcode,buf);
+        }
+        catch(DataFormatException e)
+        {
+            LOG.warn(e);
+            getConnection().close(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,e.toString());
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.jetty.websocket.AbstractExtension#addFrame(byte, byte, byte[], int, int)
+     */
+    @Override
+    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        if (getConnection().isControl(opcode) || length<_minLength)
+        {
+            super.addFrame(clearFlag(flags,1),opcode,content,offset,length);
+            return;
+        }
+
+        // prepare the uncompressed input
+        _deflater.reset();
+        _deflater.setInput(content,offset,length);
+        _deflater.finish();
+
+        // prepare the output buffer
+        byte[] out= new byte[length];
+        int out_offset=0;
+
+        // write the uncompressed length
+        if (length>0xffff)
+        {
+            out[out_offset++]=0x7f;
+            out[out_offset++]=(byte)0;
+            out[out_offset++]=(byte)0;
+            out[out_offset++]=(byte)0;
+            out[out_offset++]=(byte)0;
+            out[out_offset++]=(byte)((length>>24)&0xff);
+            out[out_offset++]=(byte)((length>>16)&0xff);
+            out[out_offset++]=(byte)((length>>8)&0xff);
+            out[out_offset++]=(byte)(length&0xff);
+        }
+        else if (length >=0x7e)
+        {
+            out[out_offset++]=0x7e;
+            out[out_offset++]=(byte)(length>>8);
+            out[out_offset++]=(byte)(length&0xff);
+        }
+        else
+        {
+            out[out_offset++]=(byte)(length&0x7f);
+        }
+
+        int l = _deflater.deflate(out,out_offset,length-out_offset);
+
+        if (_deflater.finished())
+            super.addFrame(setFlag(flags,1),opcode,out,0,l+out_offset);
+        else
+            super.addFrame(clearFlag(flags,1),opcode,content,offset,length);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/Extension.java b/src/java/org/eclipse/jetty/websocket/Extension.java
new file mode 100644
index 0000000..1d2bb7a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/Extension.java
@@ -0,0 +1,31 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.util.Map;
+
+public interface Extension extends WebSocketParser.FrameHandler, WebSocketGenerator
+{
+    public String getName();
+    public String getParameterizedName();
+    
+    public boolean init(Map<String,String> parameters);
+    public void bind(WebSocket.FrameConnection connection, WebSocketParser.FrameHandler inbound, WebSocketGenerator outbound);
+    
+}
diff --git a/src/java/org/eclipse/jetty/websocket/FixedMaskGen.java b/src/java/org/eclipse/jetty/websocket/FixedMaskGen.java
new file mode 100644
index 0000000..169d383
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/FixedMaskGen.java
@@ -0,0 +1,44 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+
+package org.eclipse.jetty.websocket;
+
+
+public class FixedMaskGen implements MaskGen
+{
+    private final byte[] _mask;
+
+    public FixedMaskGen()
+    {
+        this(new byte[]{(byte)0xff,(byte)0xff,(byte)0xff,(byte)0xff});
+    }
+
+    public FixedMaskGen(byte[] mask)
+    {
+        _mask=new byte[4];
+        // Copy to avoid that external code keeps a reference
+        // to the array parameter to modify masking on-the-fly
+        System.arraycopy(mask, 0, _mask, 0, 4);
+    }
+
+    public void genMask(byte[] mask)
+    {
+        System.arraycopy(_mask, 0, mask, 0, 4);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/FragmentExtension.java b/src/java/org/eclipse/jetty/websocket/FragmentExtension.java
new file mode 100644
index 0000000..177e4c0
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/FragmentExtension.java
@@ -0,0 +1,80 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class FragmentExtension extends AbstractExtension
+{
+    private int _maxLength=-1;
+    private int _minFragments=1;
+    
+    public FragmentExtension()
+    {
+        super("fragment");
+    }
+
+    @Override
+    public boolean init(Map<String, String> parameters)
+    {
+        if(super.init(parameters))
+        {
+            _maxLength=getInitParameter("maxLength",_maxLength);
+            _minFragments=getInitParameter("minFragments",_minFragments);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        if (getConnection().isControl(opcode))
+        {
+            super.addFrame(flags,opcode,content,offset,length);
+            return;
+        }
+        
+        int fragments=1;
+        
+        while (_maxLength>0 && length>_maxLength)
+        {
+            fragments++;
+            super.addFrame((byte)(flags&~getConnection().finMask()),opcode,content,offset,_maxLength);
+            length-=_maxLength;
+            offset+=_maxLength;
+            opcode=getConnection().continuationOpcode();
+        }
+        
+        while (fragments<_minFragments)
+        {
+            int frag=length/2;
+            fragments++;
+            super.addFrame((byte)(flags&0x7),opcode,content,offset,frag);
+            length-=frag;
+            offset+=frag;
+            opcode=getConnection().continuationOpcode();
+        }
+
+        super.addFrame((byte)(flags|getConnection().finMask()),opcode,content,offset,length);
+    }
+    
+    
+}
diff --git a/src/java/org/eclipse/jetty/websocket/IdentityExtension.java b/src/java/org/eclipse/jetty/websocket/IdentityExtension.java
new file mode 100644
index 0000000..59ba279
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/IdentityExtension.java
@@ -0,0 +1,27 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+public class IdentityExtension extends AbstractExtension
+{
+    public IdentityExtension()
+    {
+        super("identity");
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/MaskGen.java b/src/java/org/eclipse/jetty/websocket/MaskGen.java
new file mode 100644
index 0000000..a9adf64
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/MaskGen.java
@@ -0,0 +1,24 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+public interface MaskGen
+{
+    void genMask(byte[] mask);
+}
diff --git a/src/java/org/eclipse/jetty/websocket/RandomMaskGen.java b/src/java/org/eclipse/jetty/websocket/RandomMaskGen.java
new file mode 100644
index 0000000..771bc31
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/RandomMaskGen.java
@@ -0,0 +1,45 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.util.Random;
+
+
+public class RandomMaskGen implements MaskGen
+{
+    private final Random _random;
+
+    public RandomMaskGen()
+    {
+        this(new Random());
+    }
+
+    public RandomMaskGen(Random random)
+    {
+        _random=random;
+    }
+
+    public void genMask(byte[] mask)
+    {
+        // The assumption is that this code is always called
+        // with an external lock held to prevent concurrent access
+        // Otherwise we need to synchronize on the _random.
+        _random.nextBytes(mask);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocket.java b/src/java/org/eclipse/jetty/websocket/WebSocket.java
new file mode 100644
index 0000000..29dbc04
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocket.java
@@ -0,0 +1,275 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+/**
+ * WebSocket Interface.
+ * <p>
+ * This interface provides the signature for a server-side end point of a websocket connection.
+ * The Interface has several nested interfaces, for each type of message that may be received.
+ */
+public interface WebSocket
+{   
+    /**
+     * Called when a new websocket connection is accepted.
+     * @param connection The Connection object to use to send messages.
+     */
+    void onOpen(Connection connection);
+
+    /**
+     * Called when an established websocket connection closes
+     * @param closeCode
+     * @param message
+     */
+    void onClose(int closeCode, String message);
+
+    /**
+     * A nested WebSocket interface for receiving text messages
+     */
+    interface OnTextMessage extends WebSocket
+    {
+        /**
+         * Called with a complete text message when all fragments have been received.
+         * The maximum size of text message that may be aggregated from multiple frames is set with {@link Connection#setMaxTextMessageSize(int)}.
+         * @param data The message
+         */
+        void onMessage(String data);
+    }
+
+    /**
+     * A nested WebSocket interface for receiving binary messages
+     */
+    interface OnBinaryMessage extends WebSocket
+    {
+        /**
+         * Called with a complete binary message when all fragments have been received.
+         * The maximum size of binary message that may be aggregated from multiple frames is set with {@link Connection#setMaxBinaryMessageSize(int)}.
+         * @param data
+         * @param offset
+         * @param length
+         */
+        void onMessage(byte[] data, int offset, int length);
+    }
+    
+    /**
+     * A nested WebSocket interface for receiving control messages
+     */
+    interface OnControl extends WebSocket
+    {
+        /** 
+         * Called when a control message has been received.
+         * @param controlCode
+         * @param data
+         * @param offset
+         * @param length
+         * @return true if this call has completely handled the control message and no further processing is needed.
+         */
+        boolean onControl(byte controlCode,byte[] data, int offset, int length);
+    }
+    
+    /**
+     * A nested WebSocket interface for receiving any websocket frame
+     */
+    interface OnFrame extends WebSocket
+    {
+        /**
+         * Called when any websocket frame is received.
+         * @param flags
+         * @param opcode
+         * @param data
+         * @param offset
+         * @param length
+         * @return true if this call has completely handled the frame and no further processing is needed (including aggregation and/or message delivery)
+         */
+        boolean onFrame(byte flags,byte opcode,byte[] data, int offset, int length);
+        
+        void onHandshake(FrameConnection connection);
+    }
+    
+    /**
+     * A  Connection interface is passed to a WebSocket instance via the {@link WebSocket#onOpen(Connection)} to 
+     * give the application access to the specifics of the current connection.   This includes methods 
+     * for sending frames and messages as well as methods for interpreting the flags and opcodes of the connection.
+     */
+    public interface Connection
+    {
+        String getProtocol();
+        void sendMessage(String data) throws IOException;
+        void sendMessage(byte[] data, int offset, int length) throws IOException;
+        
+        /**
+         * @deprecated Use {@link #close()}
+         */
+        void disconnect();
+
+        /** 
+         * Close the connection with normal close code.
+         */
+        void close();
+        
+        /** Close the connection with specific closeCode and message.
+         * @param closeCode The close code to send, or -1 for no close code
+         * @param message The message to send or null for no message
+         */
+        void close(int closeCode,String message);
+        
+        boolean isOpen();
+
+        /**
+         * @param ms The time in ms that the connection can be idle before closing
+         */
+        void setMaxIdleTime(int ms);
+        
+        /**
+         * @param size size<0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
+         */
+        void setMaxTextMessageSize(int size);
+        
+        /**
+         * @param size size<0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
+         */
+        void setMaxBinaryMessageSize(int size);
+        
+        /**
+         * @return The time in ms that the connection can be idle before closing
+         */
+        int getMaxIdleTime();
+        
+        /**
+         * Size in characters of the maximum text message to be received
+         * @return size <0 No aggregation of frames to messages, >=0 max size of text frame aggregation buffer in characters
+         */
+        int getMaxTextMessageSize();
+        
+        /**
+         * Size in bytes of the maximum binary message to be received
+         * @return size <0 no aggregation of binary frames, >=0 size of binary frame aggregation buffer
+         */
+        int getMaxBinaryMessageSize();
+    }
+
+    /**
+     * Frame Level Connection
+     * <p>The Connection interface at the level of sending/receiving frames rather than messages.
+     * Also contains methods to decode/generate flags and opcodes without using constants, so that 
+     * code can be written to work with multiple drafts of the protocol.
+     *
+     */
+    public interface FrameConnection extends Connection
+    {
+        /**
+         * @return The opcode of a binary message
+         */
+        byte binaryOpcode();
+        
+        /**
+         * @return The opcode of a text message
+         */
+        byte textOpcode();
+        
+        /**
+         * @return The opcode of a continuation frame
+         */
+        byte continuationOpcode();
+        
+        /**
+         * @return Mask for the FIN bit.
+         */
+        byte finMask();
+        
+        /** Set if frames larger than the frame buffer are handled with local fragmentations
+         * @param allowFragmentation
+         */
+        void setAllowFrameFragmentation(boolean allowFragmentation);
+
+        /**
+         * @param flags The flags bytes of a frame
+         * @return True of the flags indicate a final frame.
+         */
+        boolean isMessageComplete(byte flags);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is for a control frame
+         */
+        boolean isControl(byte opcode);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is for a text frame
+         */
+        boolean isText(byte opcode);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is for a binary frame
+         */
+        boolean isBinary(byte opcode);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is for a continuation frame
+         */
+        boolean isContinuation(byte opcode);
+
+        /**
+         * @param opcode 
+         * @return True if the opcode is a close control
+         */
+        boolean isClose(byte opcode);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is a ping control
+         */
+        boolean isPing(byte opcode);
+
+        /**
+         * @param opcode
+         * @return True if the opcode is a pong control
+         */
+        boolean isPong(byte opcode);
+        
+        /**
+         * @return True if frames larger than the frame buffer are fragmented.
+         */
+        boolean isAllowFrameFragmentation();
+        
+        /** Send a control frame
+         * @param control
+         * @param data
+         * @param offset
+         * @param length
+         * @throws IOException
+         */
+        void sendControl(byte control,byte[] data, int offset, int length) throws IOException;
+
+        /** Send an arbitrary frame
+         * @param flags
+         * @param opcode
+         * @param data
+         * @param offset
+         * @param length
+         * @throws IOException
+         */
+        void sendFrame(byte flags,byte opcode,byte[] data, int offset, int length) throws IOException;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketBuffers.java b/src/java/org/eclipse/jetty/websocket/WebSocketBuffers.java
new file mode 100644
index 0000000..4ee126a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketBuffers.java
@@ -0,0 +1,65 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.Buffers.Type;
+import org.eclipse.jetty.io.BuffersFactory;
+
+
+/* ------------------------------------------------------------ */
+/** The WebSocket Buffer Pool.
+ *
+ * The normal buffers are byte array buffers so that user processes
+ * can access directly.   However the generator uses direct buffers
+ * for the final output stage as they are filled in bulk and are more
+ * efficient to flush.
+ */
+public class WebSocketBuffers
+{
+    final private int _bufferSize;
+    final private Buffers _buffers;
+
+    public WebSocketBuffers(final int bufferSize)
+    {
+        _bufferSize=bufferSize;
+        _buffers = BuffersFactory.newBuffers(Type.DIRECT,bufferSize,Type.INDIRECT,bufferSize,Type.INDIRECT,-1);
+    }
+
+    public Buffer getBuffer()
+    {
+        return _buffers.getBuffer();
+    }
+
+    public Buffer getDirectBuffer()
+    {
+        return _buffers.getHeader();
+    }
+
+    public void returnBuffer(Buffer buffer)
+    {
+        _buffers.returnBuffer(buffer);
+    }
+
+    public int getBufferSize()
+    {
+        return _bufferSize;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketClient.java b/src/java/org/eclipse/jetty/websocket/WebSocketClient.java
new file mode 100644
index 0000000..dff6a2f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketClient.java
@@ -0,0 +1,617 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProtocolException;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * <p>{@link WebSocketClient} allows to create multiple connections to multiple destinations
+ * that can speak the websocket protocol.</p>
+ * <p>When creating websocket connections, {@link WebSocketClient} accepts a {@link WebSocket}
+ * object (to receive events from the server), and returns a {@link WebSocket.Connection} to
+ * send data to the server.</p>
+ * <p>Example usage is as follows:</p>
+ * <pre>
+ *   WebSocketClientFactory factory = new WebSocketClientFactory();
+ *   factory.start();
+ *
+ *   WebSocketClient client = factory.newWebSocketClient();
+ *   // Configure the client
+ *
+ *   WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"), new WebSocket.OnTextMessage()
+ *   {
+ *     public void onOpen(Connection connection)
+ *     {
+ *       // open notification
+ *     }
+ *
+ *     public void onClose(int closeCode, String message)
+ *     {
+ *       // close notification
+ *     }
+ *
+ *     public void onMessage(String data)
+ *     {
+ *       // handle incoming message
+ *     }
+ *   }).get(5, TimeUnit.SECONDS);
+ *
+ *   connection.sendMessage("Hello World");
+ * </pre>
+ */
+public class WebSocketClient
+{
+    private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getName());
+
+    private final WebSocketClientFactory _factory;
+    private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
+    private final List<String> _extensions=new CopyOnWriteArrayList<String>();
+    private String _origin;
+    private String _protocol;
+    private int _maxIdleTime=-1;
+    private int _maxTextMessageSize=16*1024;
+    private int _maxBinaryMessageSize=-1;
+    private MaskGen _maskGen;
+    private SocketAddress _bindAddress;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates a WebSocketClient from a private WebSocketClientFactory.</p>
+     * <p>This can be wasteful of resources if many clients are created.</p>
+     *
+     * @deprecated Use {@link WebSocketClientFactory#newWebSocketClient()}
+     * @throws Exception if the private WebSocketClientFactory fails to start
+     */
+    @Deprecated
+    public WebSocketClient() throws Exception
+    {
+        _factory=new WebSocketClientFactory();
+        _factory.start();
+        _maskGen=_factory.getMaskGen();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates a WebSocketClient with shared WebSocketClientFactory.</p>
+     *
+     * @param factory the shared {@link WebSocketClientFactory}
+     */
+    public WebSocketClient(WebSocketClientFactory factory)
+    {
+        _factory=factory;
+        _maskGen=_factory.getMaskGen();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The WebSocketClientFactory this client was created with.
+     */
+    public WebSocketClientFactory getFactory()
+    {
+        return _factory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the address to bind the socket channel to
+     * @see #setBindAddress(SocketAddress)
+     */
+    public SocketAddress getBindAddress()
+    {
+        return _bindAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bindAddress the address to bind the socket channel to
+     * @see #getBindAddress()
+     */
+    public void setBindAddress(SocketAddress bindAddress)
+    {
+        this._bindAddress = bindAddress;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The maxIdleTime in ms for connections opened by this client,
+     * or -1 if the default from {@link WebSocketClientFactory#getSelectorManager()} is used.
+     * @see #setMaxIdleTime(int)
+     */
+    public int getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maxIdleTime The max idle time in ms for connections opened by this client
+     * @see #getMaxIdleTime()
+     */
+    public void setMaxIdleTime(int maxIdleTime)
+    {
+        _maxIdleTime=maxIdleTime;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The subprotocol string for connections opened by this client.
+     * @see #setProtocol(String)
+     */
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param protocol The subprotocol string for connections opened by this client.
+     * @see #getProtocol()
+     */
+    public void setProtocol(String protocol)
+    {
+        _protocol = protocol;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The origin URI of the client
+     * @see #setOrigin(String)
+     */
+    public String getOrigin()
+    {
+        return _origin;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param origin The origin URI of the client (eg "http://example.com")
+     * @see #getOrigin()
+     */
+    public void setOrigin(String origin)
+    {
+        _origin = origin;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Returns the map of the cookies that are sent during the initial HTTP handshake
+     * that upgrades to the websocket protocol.</p>
+     * @return The read-write cookie map
+     */
+    public Map<String,String> getCookies()
+    {
+        return _cookies;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The list of websocket protocol extensions
+     */
+    public List<String> getExtensions()
+    {
+        return _extensions;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the mask generator to use, or null if not mask generator should be used
+     * @see #setMaskGen(MaskGen)
+     */
+    public MaskGen getMaskGen()
+    {
+        return _maskGen;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maskGen the mask generator to use, or null if not mask generator should be used
+     * @see #getMaskGen()
+     */
+    public void setMaskGen(MaskGen maskGen)
+    {
+        _maskGen = maskGen;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The initial maximum text message size (in characters) for a connection
+     */
+    public int getMaxTextMessageSize()
+    {
+        return _maxTextMessageSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the initial maximum text message size for a connection. This can be changed by
+     * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}.
+     * @param maxTextMessageSize The default maximum text message size (in characters) for a connection
+     */
+    public void setMaxTextMessageSize(int maxTextMessageSize)
+    {
+        _maxTextMessageSize = maxTextMessageSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return The initial maximum binary message size (in bytes)  for a connection
+     */
+    public int getMaxBinaryMessageSize()
+    {
+        return _maxBinaryMessageSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set the initial maximum binary message size for a connection. This can be changed by
+     * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}.
+     * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection
+     */
+    public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
+    {
+        _maxBinaryMessageSize = maxBinaryMessageSize;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Opens a websocket connection to the URI and blocks until the connection is accepted or there is an error.</p>
+     *
+     * @param uri The URI to connect to.
+     * @param websocket The {@link WebSocket} instance to handle incoming events.
+     * @param maxConnectTime The interval to wait for a successful connection
+     * @param units the units of the maxConnectTime
+     * @return A {@link WebSocket.Connection}
+     * @throws IOException if the connection fails
+     * @throws InterruptedException if the thread is interrupted
+     * @throws TimeoutException if the timeout elapses before the connection is completed
+     * @see #open(URI, WebSocket)
+     */
+    public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
+    {
+        try
+        {
+            return open(uri,websocket).get(maxConnectTime,units);
+        }
+        catch (ExecutionException e)
+        {
+            Throwable cause = e.getCause();
+            if (cause instanceof IOException)
+                throw (IOException)cause;
+            if (cause instanceof Error)
+                throw (Error)cause;
+            if (cause instanceof RuntimeException)
+                throw (RuntimeException)cause;
+            throw new RuntimeException(cause);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Asynchronously opens a websocket connection and returns a {@link Future} to obtain the connection.</p>
+     * <p>The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.</p>
+     *
+     * @param uri The URI to connect to.
+     * @param websocket The {@link WebSocket} instance to handle incoming events.
+     * @return A {@link Future} to the {@link WebSocket.Connection}
+     * @throws IOException if the connection fails
+     * @see #open(URI, WebSocket, long, TimeUnit)
+     */
+    public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
+    {
+        if (!_factory.isStarted())
+            throw new IllegalStateException("Factory !started");
+
+        InetSocketAddress address = toSocketAddress(uri);
+
+        SocketChannel channel = null;
+        try
+        {
+            channel = SocketChannel.open();
+            if (_bindAddress != null)
+                channel.socket().bind(_bindAddress);
+            channel.socket().setTcpNoDelay(true);
+
+            WebSocketFuture holder = new WebSocketFuture(websocket,uri,this,channel);
+
+            channel.configureBlocking(false);
+            channel.connect(address);
+            _factory.getSelectorManager().register(channel,holder);
+
+            return holder;
+        }
+        catch (RuntimeException e)
+        {
+            // close the channel (prevent connection leak)
+            IO.close(channel);
+            
+            // rethrow
+            throw e;
+        }
+        catch(IOException e)
+        {
+            // close the channel (prevent connection leak)
+            IO.close(channel);
+
+            // rethrow
+            throw e;
+        }
+    }
+
+    public static InetSocketAddress toSocketAddress(URI uri)
+    {
+        String scheme = uri.getScheme();
+        if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
+            throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme);
+        int port = uri.getPort();
+        if (port == 0)
+            throw new IllegalArgumentException("Bad WebSocket port: " + port);
+        if (port < 0)
+            port = "ws".equals(scheme) ? 80 : 443;
+
+        return new InetSocketAddress(uri.getHost(), port);
+    }
+
+    /* ------------------------------------------------------------ */
+    /** The Future Websocket Connection.
+     */
+    static class WebSocketFuture implements Future<WebSocket.Connection>
+    {
+        final WebSocket _websocket;
+        final URI _uri;
+        final WebSocketClient _client;
+        final CountDownLatch _done = new CountDownLatch(1);
+        ByteChannel _channel;
+        WebSocketConnection _connection;
+        Throwable _exception;
+
+        private WebSocketFuture(WebSocket websocket, URI uri, WebSocketClient client, ByteChannel channel)
+        {
+            _websocket=websocket;
+            _uri=uri;
+            _client=client;
+            _channel=channel;
+        }
+
+        public void onConnection(WebSocketConnection connection)
+        {
+            try
+            {
+                _client.getFactory().addConnection(connection);
+
+                connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize());
+                connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize());
+
+                WebSocketConnection con;
+                synchronized (this)
+                {
+                    if (_channel!=null)
+                        _connection=connection;
+                    con=_connection;
+                }
+
+                if (con!=null)
+                {
+                    if (_websocket instanceof WebSocket.OnFrame)
+                        ((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)con.getConnection());
+
+                    _websocket.onOpen(con.getConnection());
+                }
+            }
+            finally
+            {
+                _done.countDown();
+            }
+        }
+
+        public void handshakeFailed(Throwable ex)
+        {
+            try
+            {
+                ByteChannel channel=null;
+                synchronized (this)
+                {
+                    if (_channel!=null)
+                    {
+                        channel=_channel;
+                        _channel=null;
+                        _exception=ex;
+                    }
+                }
+
+                if (channel!=null)
+                {
+                    if (ex instanceof ProtocolException)
+                        closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_PROTOCOL,ex.getMessage());
+                    else
+                        closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,ex.getMessage());
+                }
+            }
+            finally
+            {
+                _done.countDown();
+            }
+        }
+
+        public Map<String,String> getCookies()
+        {
+            return _client.getCookies();
+        }
+
+        public String getProtocol()
+        {
+            return _client.getProtocol();
+        }
+
+        public WebSocket getWebSocket()
+        {
+            return _websocket;
+        }
+
+        public URI getURI()
+        {
+            return _uri;
+        }
+
+        public int getMaxIdleTime()
+        {
+            return _client.getMaxIdleTime();
+        }
+
+        public String getOrigin()
+        {
+            return _client.getOrigin();
+        }
+
+        public MaskGen getMaskGen()
+        {
+            return _client.getMaskGen();
+        }
+
+        @Override
+        public String toString()
+        {
+            return "[" + _uri + ","+_websocket+"]@"+hashCode();
+        }
+
+        public boolean cancel(boolean mayInterruptIfRunning)
+        {
+            try
+            {
+                ByteChannel channel=null;
+                synchronized (this)
+                {
+                    if (_connection==null && _exception==null && _channel!=null)
+                    {
+                        channel=_channel;
+                        _channel=null;
+                    }
+                }
+
+                if (channel!=null)
+                {
+                    closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"cancelled");
+                    return true;
+                }
+                return false;
+            }
+            finally
+            {
+                _done.countDown();
+            }
+        }
+
+        public boolean isCancelled()
+        {
+            synchronized (this)
+            {
+                return _channel==null && _connection==null;
+            }
+        }
+
+        public boolean isDone()
+        {
+            synchronized (this)
+            {
+                return _connection!=null && _exception==null;
+            }
+        }
+
+        public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
+        {
+            try
+            {
+                return get(Long.MAX_VALUE,TimeUnit.SECONDS);
+            }
+            catch(TimeoutException e)
+            {
+                throw new IllegalStateException("The universe has ended",e);
+            }
+        }
+
+        public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
+                TimeoutException
+        {
+            _done.await(timeout,unit);
+            ByteChannel channel=null;
+            org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
+            Throwable exception;
+            synchronized (this)
+            {
+                exception=_exception;
+                if (_connection==null)
+                {
+                    exception=_exception;
+                    channel=_channel;
+                    _channel=null;
+                }
+                else
+                    connection=_connection.getConnection();
+            }
+
+            if (channel!=null)
+                closeChannel(channel,WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"timeout");
+            if (exception!=null)
+                throw new ExecutionException(exception);
+            if (connection!=null)
+                return connection;
+            throw new TimeoutException();
+        }
+
+        private void closeChannel(ByteChannel channel,int code, String message)
+        {
+            try
+            {
+                _websocket.onClose(code,message);
+            }
+            catch(Exception e)
+            {
+                __log.warn(e);
+            }
+
+            try
+            {
+                channel.close();
+            }
+            catch(IOException e)
+            {
+                __log.debug(e);
+            }
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/src/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
new file mode 100644
index 0000000..4a701a5
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
@@ -0,0 +1,574 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.ProtocolException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import javax.net.ssl.SSLEngine;
+
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.SimpleBuffers;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
+import org.eclipse.jetty.io.nio.SelectorManager;
+import org.eclipse.jetty.io.nio.SslConnection;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.component.AggregateLifeCycle;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+
+/* ------------------------------------------------------------ */
+/**
+ * <p>WebSocketClientFactory contains the common components needed by multiple {@link WebSocketClient} instances
+ * (for example, a {@link ThreadPool}, a {@link SelectorManager NIO selector}, etc).</p>
+ * <p>WebSocketClients with different configurations should share the same factory to avoid to waste resources.</p>
+ * <p>If a ThreadPool or MaskGen is passed in the constructor, then it is not added with {@link AggregateLifeCycle#addBean(Object)},
+ * so it's lifecycle must be controlled externally.
+ *
+ * @see WebSocketClient
+ */
+public class WebSocketClientFactory extends AggregateLifeCycle
+{
+    private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName());
+    private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
+    private final Queue<WebSocketConnection> connections = new ConcurrentLinkedQueue<WebSocketConnection>();
+    private final SslContextFactory _sslContextFactory = new SslContextFactory();
+    private final ThreadPool _threadPool;
+    private final WebSocketClientSelector _selector;
+    private MaskGen _maskGen;
+    private WebSocketBuffers _buffers;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates a WebSocketClientFactory with the default configuration.</p>
+     */
+    public WebSocketClientFactory()
+    {
+        this(null);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates a WebSocketClientFactory with the given ThreadPool and the default configuration.</p>
+     *
+     * @param threadPool the ThreadPool instance to use
+     */
+    public WebSocketClientFactory(ThreadPool threadPool)
+    {
+        this(threadPool, new RandomMaskGen());
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates a WebSocketClientFactory with the given ThreadPool and the given MaskGen.</p>
+     *
+     * @param threadPool the ThreadPool instance to use
+     * @param maskGen    the MaskGen instance to use
+     */
+    public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen)
+    {
+        this(threadPool, maskGen, 8192);
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * <p>Creates a WebSocketClientFactory with the specified configuration.</p>
+     *
+     * @param threadPool the ThreadPool instance to use
+     * @param maskGen    the mask generator to use
+     * @param bufferSize the read buffer size
+     */
+    public WebSocketClientFactory(ThreadPool threadPool, MaskGen maskGen, int bufferSize)
+    {
+        if (threadPool == null)
+            threadPool = new QueuedThreadPool();
+        _threadPool = threadPool;
+        addBean(_threadPool);
+
+        _buffers = new WebSocketBuffers(bufferSize);
+        addBean(_buffers);
+
+        _maskGen = maskGen;
+        addBean(_maskGen);
+
+        _selector = new WebSocketClientSelector();
+        addBean(_selector);
+
+        addBean(_sslContextFactory);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the SslContextFactory used to configure SSL parameters
+     */
+    public SslContextFactory getSslContextFactory()
+    {
+        return _sslContextFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the selectorManager. Used to configure the manager.
+     *
+     * @return The {@link SelectorManager} instance.
+     */
+    public SelectorManager getSelectorManager()
+    {
+        return _selector;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Get the ThreadPool.
+     * Used to set/query the thread pool configuration.
+     *
+     * @return The {@link ThreadPool}
+     */
+    public ThreadPool getThreadPool()
+    {
+        return _threadPool;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the shared mask generator, or null if no shared mask generator is used
+     * @see WebSocketClient#getMaskGen()
+     */
+    public MaskGen getMaskGen()
+    {
+        return _maskGen;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param maskGen the shared mask generator, or null if no shared mask generator is used
+     * @see WebSocketClient#setMaskGen(MaskGen)
+     */
+    public void setMaskGen(MaskGen maskGen)
+    {
+        if (isRunning())
+            throw new IllegalStateException(getState());
+        removeBean(_maskGen);
+        _maskGen = maskGen;
+        addBean(maskGen);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param bufferSize the read buffer size
+     * @see #getBufferSize()
+     */
+    public void setBufferSize(int bufferSize)
+    {
+        if (isRunning())
+            throw new IllegalStateException(getState());
+        removeBean(_buffers);
+        _buffers = new WebSocketBuffers(bufferSize);
+        addBean(_buffers);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return the read buffer size
+     */
+    public int getBufferSize()
+    {
+        return _buffers.getBufferSize();
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        closeConnections();
+        super.doStop();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * <p>Creates and returns a new instance of a {@link WebSocketClient}, configured with this
+     * WebSocketClientFactory instance.</p>
+     *
+     * @return a new {@link WebSocketClient} instance
+     */
+    public WebSocketClient newWebSocketClient()
+    {
+        return new WebSocketClient(this);
+    }
+
+    protected SSLEngine newSslEngine(SocketChannel channel) throws IOException
+    {
+        SSLEngine sslEngine;
+        if (channel != null)
+        {
+            String peerHost = channel.socket().getInetAddress().getHostAddress();
+            int peerPort = channel.socket().getPort();
+            sslEngine = _sslContextFactory.newSslEngine(peerHost, peerPort);
+        }
+        else
+        {
+            sslEngine = _sslContextFactory.newSslEngine();
+        }
+        sslEngine.setUseClientMode(true);
+        sslEngine.beginHandshake();
+
+        return sslEngine;
+    }
+
+    protected boolean addConnection(WebSocketConnection connection)
+    {
+        return isRunning() && connections.add(connection);
+    }
+
+    protected boolean removeConnection(WebSocketConnection connection)
+    {
+        return connections.remove(connection);
+    }
+
+    protected void closeConnections()
+    {
+        for (WebSocketConnection connection : connections)
+            connection.shutdown();
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * WebSocket Client Selector Manager
+     */
+    class WebSocketClientSelector extends SelectorManager
+    {
+        @Override
+        public boolean dispatch(Runnable task)
+        {
+            return _threadPool.dispatch(task);
+        }
+
+        @Override
+        protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, final SelectionKey key) throws IOException
+        {
+            WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)key.attachment();
+            int maxIdleTime = holder.getMaxIdleTime();
+            if (maxIdleTime < 0)
+                maxIdleTime = (int)getMaxIdleTime();
+            SelectChannelEndPoint result = new SelectChannelEndPoint(channel, selectSet, key, maxIdleTime);
+            AsyncEndPoint endPoint = result;
+
+            // Detect if it is SSL, and wrap the connection if so
+            if ("wss".equals(holder.getURI().getScheme()))
+            {
+                SSLEngine sslEngine = newSslEngine(channel);
+                SslConnection sslConnection = new SslConnection(sslEngine, endPoint);
+                endPoint.setConnection(sslConnection);
+                endPoint = sslConnection.getSslEndPoint();
+            }
+
+            AsyncConnection connection = selectSet.getManager().newConnection(channel, endPoint, holder);
+            endPoint.setConnection(connection);
+
+            return result;
+        }
+
+        @Override
+        public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
+        {
+            WebSocketClient.WebSocketFuture holder = (WebSocketClient.WebSocketFuture)attachment;
+            return new HandshakeConnection(endpoint, holder);
+        }
+
+        @Override
+        protected void endPointOpened(SelectChannelEndPoint endpoint)
+        {
+            // TODO expose on outer class ??
+        }
+
+        @Override
+        protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
+        {
+            LOG.debug("upgrade {} -> {}", oldConnection, endpoint.getConnection());
+        }
+
+        @Override
+        protected void endPointClosed(SelectChannelEndPoint endpoint)
+        {
+            endpoint.getConnection().onClose();
+        }
+
+        @Override
+        protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
+        {
+            if (!(attachment instanceof WebSocketClient.WebSocketFuture))
+                super.connectionFailed(channel, ex, attachment);
+            else
+            {
+                __log.debug(ex);
+                WebSocketClient.WebSocketFuture future = (WebSocketClient.WebSocketFuture)attachment;
+
+                future.handshakeFailed(ex);
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Handshake Connection.
+     * Handles the connection until the handshake succeeds or fails.
+     */
+    class HandshakeConnection extends AbstractConnection implements AsyncConnection
+    {
+        private final AsyncEndPoint _endp;
+        private final WebSocketClient.WebSocketFuture _future;
+        private final String _key;
+        private final HttpParser _parser;
+        private String _accept;
+        private String _error;
+        private ByteArrayBuffer _handshake;
+
+        public HandshakeConnection(AsyncEndPoint endpoint, WebSocketClient.WebSocketFuture future)
+        {
+            super(endpoint, System.currentTimeMillis());
+            _endp = endpoint;
+            _future = future;
+
+            byte[] bytes = new byte[16];
+            new Random().nextBytes(bytes);
+            _key = new String(B64Code.encode(bytes));
+
+            Buffers buffers = new SimpleBuffers(_buffers.getBuffer(), null);
+            _parser = new HttpParser(buffers, _endp, new HttpParser.EventHandler()
+            {
+                @Override
+                public void startResponse(Buffer version, int status, Buffer reason) throws IOException
+                {
+                    if (status != 101)
+                    {
+                        _error = "Bad response status " + status + " " + reason;
+                        _endp.close();
+                    }
+                }
+
+                @Override
+                public void parsedHeader(Buffer name, Buffer value) throws IOException
+                {
+                    if (__ACCEPT.equals(name))
+                        _accept = value.toString();
+                }
+
+                @Override // TODO simone says shouldn't be needed
+                public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
+                {
+                    if (_error == null)
+                        _error = "Bad response: " + method + " " + url + " " + version;
+                    _endp.close();
+                }
+
+                @Override // TODO simone says shouldn't be needed
+                public void content(Buffer ref) throws IOException
+                {
+                    if (_error == null)
+                        _error = "Bad response. " + ref.length() + "B of content?";
+                    _endp.close();
+                }
+            });
+        }
+
+        private boolean handshake()
+        {
+            if (_handshake==null)
+            {
+                String path = _future.getURI().getPath();
+                if (path == null || path.length() == 0)
+                    path = "/";
+
+                if (_future.getURI().getRawQuery() != null)
+                    path += "?" + _future.getURI().getRawQuery();
+
+                String origin = _future.getOrigin();
+
+                StringBuilder request = new StringBuilder(512);
+                request.append("GET ").append(path).append(" HTTP/1.1\r\n")
+                .append("Host: ").append(_future.getURI().getHost()).append(":")
+                .append(_future.getURI().getPort()).append("\r\n")
+                .append("Upgrade: websocket\r\n")
+                .append("Connection: Upgrade\r\n")
+                .append("Sec-WebSocket-Key: ")
+                .append(_key).append("\r\n");
+
+                if (origin != null)
+                    request.append("Origin: ").append(origin).append("\r\n");
+
+                request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n");
+
+                if (_future.getProtocol() != null)
+                    request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n");
+
+                Map<String, String> cookies = _future.getCookies();
+                if (cookies != null && cookies.size() > 0)
+                {
+                    for (String cookie : cookies.keySet())
+                        request.append("Cookie: ")
+                        .append(QuotedStringTokenizer.quoteIfNeeded(cookie, HttpFields.__COOKIE_DELIM))
+                        .append("=")
+                        .append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), HttpFields.__COOKIE_DELIM))
+                        .append("\r\n");
+                }
+
+                request.append("\r\n");
+
+                _handshake=new ByteArrayBuffer(request.toString(), false);
+            }
+            
+            // TODO extensions
+
+            try
+            {
+                int len = _handshake.length();
+                int flushed = _endp.flush(_handshake);
+                if (flushed<0)
+                    throw new IOException("incomplete handshake");
+            }
+            catch (IOException e)
+            {
+                _future.handshakeFailed(e);
+            }
+            return _handshake.length()==0;
+        }
+
+        public Connection handle() throws IOException
+        {
+            while (_endp.isOpen() && !_parser.isComplete())
+            {
+                if (_handshake==null || _handshake.length()>0)
+                    if (!handshake())
+                        return this;
+
+                if (!_parser.parseAvailable())
+                {
+                    if (_endp.isInputShutdown())
+                        _future.handshakeFailed(new IOException("Incomplete handshake response"));
+                    return this;
+                }
+            }
+            if (_error == null)
+            {
+                if (_accept == null)
+                {
+                    _error = "No Sec-WebSocket-Accept";
+                }
+                else if (!WebSocketConnectionRFC6455.hashKey(_key).equals(_accept))
+                {
+                    _error = "Bad Sec-WebSocket-Accept";
+                }
+                else
+                {
+                    WebSocketConnection connection = newWebSocketConnection();
+
+                    Buffer header = _parser.getHeaderBuffer();
+                    if (header.hasContent())
+                        connection.fillBuffersFrom(header);
+                    _buffers.returnBuffer(header);
+
+                    _future.onConnection(connection);
+
+                    return connection;
+                }
+            }
+
+            _endp.close();
+            return this;
+        }
+
+        private WebSocketConnection newWebSocketConnection() throws IOException
+        {
+            __log.debug("newWebSocketConnection()");
+            return new WebSocketClientConnection(
+                    _future._client.getFactory(),
+                    _future.getWebSocket(),
+                    _endp,
+                    _buffers,
+                    System.currentTimeMillis(),
+                    _future.getMaxIdleTime(),
+                    _future.getProtocol(),
+                    null,
+                    WebSocketConnectionRFC6455.VERSION,
+                    _future.getMaskGen());
+        }
+
+        public void onInputShutdown() throws IOException
+        {
+            _endp.close();
+        }
+
+        public boolean isIdle()
+        {
+            return false;
+        }
+
+        public boolean isSuspended()
+        {
+            return false;
+        }
+
+        public void onClose()
+        {
+            if (_error != null)
+                _future.handshakeFailed(new ProtocolException(_error));
+            else
+                _future.handshakeFailed(new EOFException());
+        }
+    }
+
+    private static class WebSocketClientConnection extends WebSocketConnectionRFC6455
+    {
+        private final WebSocketClientFactory factory;
+
+        public WebSocketClientConnection(WebSocketClientFactory factory, WebSocket webSocket, EndPoint endPoint, WebSocketBuffers buffers, long timeStamp, int maxIdleTime, String protocol, List<Extension> extensions, int draftVersion, MaskGen maskGen) throws IOException
+        {
+            super(webSocket, endPoint, buffers, timeStamp, maxIdleTime, protocol, extensions, draftVersion, maskGen);
+            this.factory = factory;
+        }
+
+        @Override
+        public void onClose()
+        {
+            super.onClose();
+            factory.removeConnection(this);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketConnection.java b/src/java/org/eclipse/jetty/websocket/WebSocketConnection.java
new file mode 100644
index 0000000..8412feb
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketConnection.java
@@ -0,0 +1,36 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+
+import java.util.List;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.nio.AsyncConnection;
+
+public interface WebSocketConnection extends AsyncConnection
+{
+    void fillBuffersFrom(Buffer buffer);
+
+    List<Extension> getExtensions();
+
+    WebSocket.Connection getConnection();
+
+    void shutdown();
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
new file mode 100644
index 0000000..06740a1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
@@ -0,0 +1,514 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.OnFrame;
+
+public class WebSocketConnectionD00 extends AbstractConnection implements WebSocketConnection, WebSocket.FrameConnection
+{
+    private static final Logger LOG = Log.getLogger(WebSocketConnectionD00.class);
+
+    public final static byte LENGTH_FRAME=(byte)0x80;
+    public final static byte SENTINEL_FRAME=(byte)0x00;
+
+    private final WebSocketParser _parser;
+    private final WebSocketGenerator _generator;
+    private final WebSocket _websocket;
+    private final String _protocol;
+    private String _key1;
+    private String _key2;
+    private ByteArrayBuffer _hixieBytes;
+
+    public WebSocketConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
+        throws IOException
+    {
+        super(endpoint,timestamp);
+
+        _endp.setMaxIdleTime(maxIdleTime);
+
+        _websocket = websocket;
+        _protocol=protocol;
+
+        _generator = new WebSocketGeneratorD00(buffers, _endp);
+        _parser = new WebSocketParserD00(buffers, endpoint, new FrameHandlerD00(_websocket));
+    }
+
+    /* ------------------------------------------------------------ */
+    public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
+    {
+        return this;
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public void setHixieKeys(String key1,String key2)
+    {
+        _key1=key1;
+        _key2=key2;
+        _hixieBytes=new IndirectNIOBuffer(16);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection handle() throws IOException
+    {
+        try
+        {
+            // handle stupid hixie random bytes
+            if (_hixieBytes!=null)
+            {
+
+                // take any available bytes from the parser buffer, which may have already been read
+                Buffer buffer=_parser.getBuffer();
+                if (buffer!=null && buffer.length()>0)
+                {
+                    int l=buffer.length();
+                    if (l>(8-_hixieBytes.length()))
+                        l=8-_hixieBytes.length();
+                    _hixieBytes.put(buffer.peek(buffer.getIndex(),l));
+                    buffer.skip(l);
+                }
+
+                // while we are not blocked
+                while(_endp.isOpen())
+                {
+                    // do we now have enough
+                    if (_hixieBytes.length()==8)
+                    {
+                        // we have the silly random bytes
+                        // so let's work out the stupid 16 byte reply.
+                        doTheHixieHixieShake();
+                        _endp.flush(_hixieBytes);
+                        _hixieBytes=null;
+                        _endp.flush();
+                        break;
+                    }
+
+                    // no, then let's fill
+                    int filled=_endp.fill(_hixieBytes);
+                    if (filled<0)
+                    {
+                        _endp.flush();
+                        _endp.close();
+                        break;
+                    }
+                    else if (filled==0)
+                        return this;
+                }
+
+                if (_websocket instanceof OnFrame)
+                    ((OnFrame)_websocket).onHandshake(this);
+                _websocket.onOpen(this);
+                return this;
+            }
+
+            // handle the framing protocol
+            boolean progress=true;
+
+            while (progress)
+            {
+                int flushed=_generator.flush();
+                int filled=_parser.parseNext();
+
+                progress = flushed>0 || filled>0;
+
+                _endp.flush();
+
+                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
+                    progress=true;
+            }
+        }
+        catch(IOException e)
+        {
+            LOG.debug(e);
+            try
+            {
+                if (_endp.isOpen())
+                    _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+            throw e;
+        }
+        finally
+        {
+            if (_endp.isOpen())
+            {
+                if (_endp.isInputShutdown() && _generator.isBufferEmpty())
+                    _endp.close();
+                else
+                    checkWriteable();
+
+                checkWriteable();
+            }
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onInputShutdown() throws IOException
+    {
+        // TODO
+    }
+
+    /* ------------------------------------------------------------ */
+    private void doTheHixieHixieShake()
+    {
+        byte[] result=WebSocketConnectionD00.doTheHixieHixieShake(
+                WebSocketConnectionD00.hixieCrypt(_key1),
+                WebSocketConnectionD00.hixieCrypt(_key2),
+                _hixieBytes.asArray());
+        _hixieBytes.clear();
+        _hixieBytes.put(result);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isOpen()
+    {
+        return _endp!=null&&_endp.isOpen();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _parser.isBufferEmpty() && _generator.isBufferEmpty();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        _websocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     */
+    public void sendMessage(String content) throws IOException
+    {
+        byte[] data = content.getBytes(StringUtil.__UTF8);
+        _generator.addFrame((byte)0,SENTINEL_FRAME,data,0,data.length);
+        _generator.flush();
+        checkWriteable();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void sendMessage(byte[] data, int offset, int length) throws IOException
+    {
+        _generator.addFrame((byte)0,LENGTH_FRAME,data,offset,length);
+        _generator.flush();
+        checkWriteable();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isMore(byte flags)
+    {
+        return (flags&0x8) != 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * {@inheritDoc}
+     */
+    public void sendControl(byte code, byte[] content, int offset, int length) throws IOException
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        _generator.addFrame((byte)0,opcode,content,offset,length);
+        _generator.flush();
+        checkWriteable();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void close(int code, String message)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void disconnect()
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void close()
+    {
+        try
+        {
+            _generator.flush();
+            _endp.close();
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+        }
+    }
+
+    public void shutdown()
+    {
+        close();
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fillBuffersFrom(Buffer buffer)
+    {
+        _parser.fill(buffer);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    private void checkWriteable()
+    {
+        if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
+            ((AsyncEndPoint)_endp).scheduleWrite();
+    }
+
+    /* ------------------------------------------------------------ */
+    static long hixieCrypt(String key)
+    {
+        // Don't ask me what all this is about.
+        // I think it's pretend secret stuff, kind of
+        // like talking in pig latin!
+        long number=0;
+        int spaces=0;
+        for (char c : key.toCharArray())
+        {
+            if (Character.isDigit(c))
+                number=number*10+(c-'0');
+            else if (c==' ')
+                spaces++;
+        }
+        return number/spaces;
+    }
+
+    public static byte[] doTheHixieHixieShake(long key1,long key2,byte[] key3)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte [] fodder = new byte[16];
+
+            fodder[0]=(byte)(0xff&(key1>>24));
+            fodder[1]=(byte)(0xff&(key1>>16));
+            fodder[2]=(byte)(0xff&(key1>>8));
+            fodder[3]=(byte)(0xff&key1);
+            fodder[4]=(byte)(0xff&(key2>>24));
+            fodder[5]=(byte)(0xff&(key2>>16));
+            fodder[6]=(byte)(0xff&(key2>>8));
+            fodder[7]=(byte)(0xff&key2);
+            System.arraycopy(key3, 0, fodder, 8, 8);
+            md.update(fodder);
+            return md.digest();
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    public void setMaxTextMessageSize(int size)
+    {
+    }
+
+    public void setMaxIdleTime(int ms)
+    {
+        try
+        {
+            _endp.setMaxIdleTime(ms);
+        }
+        catch(IOException e)
+        {
+            LOG.warn(e);
+        }
+    }
+
+    public void setMaxBinaryMessageSize(int size)
+    {
+    }
+
+    public int getMaxTextMessageSize()
+    {
+        return -1;
+    }
+
+    public int getMaxIdleTime()
+    {
+        return _endp.getMaxIdleTime();
+    }
+
+    public int getMaxBinaryMessageSize()
+    {
+        return -1;
+    }
+
+    public String getProtocol()
+    {
+        return _protocol;
+    }
+
+    protected void onFrameHandshake()
+    {
+        if (_websocket instanceof OnFrame)
+        {
+            ((OnFrame)_websocket).onHandshake(this);
+        }
+    }
+
+    protected void onWebsocketOpen()
+    {
+        _websocket.onOpen(this);
+    }
+
+    static class FrameHandlerD00 implements WebSocketParser.FrameHandler
+    {
+        final WebSocket _websocket;
+
+        FrameHandlerD00(WebSocket websocket)
+        {
+            _websocket=websocket;
+        }
+
+        public void onFrame(byte flags, byte opcode, Buffer buffer)
+        {
+            try
+            {
+                byte[] array=buffer.array();
+
+                if (opcode==0)
+                {
+                    if (_websocket instanceof WebSocket.OnTextMessage)
+                        ((WebSocket.OnTextMessage)_websocket).onMessage(buffer.toString(StringUtil.__UTF8));
+                }
+                else
+                {
+                    if (_websocket instanceof WebSocket.OnBinaryMessage)
+                        ((WebSocket.OnBinaryMessage)_websocket).onMessage(array,buffer.getIndex(),buffer.length());
+                }
+            }
+            catch(Throwable th)
+            {
+                LOG.warn(th);
+            }
+        }
+
+        public void close(int code,String message)
+        {
+        }
+    }
+
+    public boolean isMessageComplete(byte flags)
+    {
+        return true;
+    }
+
+    public byte binaryOpcode()
+    {
+        return LENGTH_FRAME;
+    }
+
+    public byte textOpcode()
+    {
+        return SENTINEL_FRAME;
+    }
+
+    public boolean isControl(byte opcode)
+    {
+        return false;
+    }
+
+    public boolean isText(byte opcode)
+    {
+        return (opcode&LENGTH_FRAME)==0;
+    }
+
+    public boolean isBinary(byte opcode)
+    {
+        return (opcode&LENGTH_FRAME)!=0;
+    }
+
+    public boolean isContinuation(byte opcode)
+    {
+        return false;
+    }
+
+    public boolean isClose(byte opcode)
+    {
+        return false;
+    }
+
+    public boolean isPing(byte opcode)
+    {
+        return false;
+    }
+
+    public boolean isPong(byte opcode)
+    {
+        return false;
+    }
+
+    public List<Extension> getExtensions()
+    {
+        return Collections.emptyList();
+    }
+
+    public byte continuationOpcode()
+    {
+        return 0;
+    }
+
+    public byte finMask()
+    {
+        return 0;
+    }
+
+    public void setAllowFrameFragmentation(boolean allowFragmentation)
+    {
+    }
+
+    public boolean isAllowFrameFragmentation()
+    {
+        return false;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
new file mode 100644
index 0000000..5b47723
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
@@ -0,0 +1,734 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
+import org.eclipse.jetty.websocket.WebSocket.OnControl;
+import org.eclipse.jetty.websocket.WebSocket.OnFrame;
+import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
+
+public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
+{
+    private static final Logger LOG = Log.getLogger(WebSocketConnectionD06.class);
+
+    final static byte OP_CONTINUATION = 0x00;
+    final static byte OP_CLOSE = 0x01;
+    final static byte OP_PING = 0x02;
+    final static byte OP_PONG = 0x03;
+    final static byte OP_TEXT = 0x04;
+    final static byte OP_BINARY = 0x05;
+
+    final static int CLOSE_NORMAL=1000;
+    final static int CLOSE_SHUTDOWN=1001;
+    final static int CLOSE_PROTOCOL=1002;
+    final static int CLOSE_BADDATA=1003;
+    final static int CLOSE_LARGE=1004;
+
+    static boolean isLastFrame(int flags)
+    {
+        return (flags&0x8)!=0;
+    }
+
+    static boolean isControlFrame(int opcode)
+    {
+        switch(opcode)
+        {
+            case OP_CLOSE:
+            case OP_PING:
+            case OP_PONG:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private final static byte[] MAGIC;
+    private final WebSocketParser _parser;
+    private final WebSocketGenerator _generator;
+    private final WebSocket _webSocket;
+    private final OnFrame _onFrame;
+    private final OnBinaryMessage _onBinaryMessage;
+    private final OnTextMessage _onTextMessage;
+    private final OnControl _onControl;
+    private final String _protocol;
+    private volatile boolean _closedIn;
+    private volatile boolean _closedOut;
+    private int _maxTextMessageSize;
+    private int _maxBinaryMessageSize=-1;
+
+    static
+    {
+        try
+        {
+            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD06();
+    private final WebSocket.FrameConnection _connection = new FrameConnectionD06();
+
+
+    /* ------------------------------------------------------------ */
+    public WebSocketConnectionD06(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
+        throws IOException
+    {
+        super(endpoint,timestamp);
+
+        _endp.setMaxIdleTime(maxIdleTime);
+
+        _webSocket = websocket;
+        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
+        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
+        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
+        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
+        _generator = new WebSocketGeneratorD06(buffers, _endp,null);
+        _parser = new WebSocketParserD06(buffers, endpoint, _frameHandler,true);
+        _protocol=protocol;
+
+        _maxTextMessageSize=buffers.getBufferSize();
+        _maxBinaryMessageSize=-1;
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebSocket.Connection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection handle() throws IOException
+    {
+        try
+        {
+            // handle the framing protocol
+            boolean progress=true;
+
+            while (progress)
+            {
+                int flushed=_generator.flush();
+                int filled=_parser.parseNext();
+
+                progress = flushed>0 || filled>0;
+
+                if (filled<0 || flushed<0)
+                {
+                    _endp.close();
+                    break;
+                }
+            }
+        }
+        catch(IOException e)
+        {
+            try
+            {
+                _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+            throw e;
+        }
+        finally
+        {
+            if (_endp.isOpen())
+            {
+                if (_closedIn && _closedOut && _generator.isBufferEmpty())
+                    _endp.close();
+                else if (_endp.isInputShutdown() && !_closedIn)
+                    closeIn(CLOSE_PROTOCOL,null);
+                else
+                    checkWriteable();
+            }
+
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onInputShutdown() throws IOException
+    {
+        // TODO
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _parser.isBufferEmpty() && _generator.isBufferEmpty();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onIdleExpired(long idleForMs)
+    {
+        closeOut(WebSocketConnectionD06.CLOSE_NORMAL,"Idle");
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        _webSocket.onClose(WebSocketConnectionD06.CLOSE_NORMAL,"");
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void closeIn(int code,String message)
+    {
+        LOG.debug("ClosedIn {} {}",this,message);
+        try
+        {
+            if (_closedOut)
+                _endp.close();
+            else
+                closeOut(code,message);
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+        }
+        finally
+        {
+            _closedIn=true;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized void closeOut(int code,String message)
+    {
+        LOG.debug("ClosedOut {} {}",this,message);
+        try
+        {
+            if (_closedIn || _closedOut)
+                _endp.close();
+            else
+            {
+                if (code<=0)
+                    code=WebSocketConnectionD06.CLOSE_NORMAL;
+                byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
+                bytes[0]=(byte)(code/0x100);
+                bytes[1]=(byte)(code%0x100);
+                _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_CLOSE,bytes,0,bytes.length);
+            }
+            _generator.flush();
+
+        }
+        catch(IOException e)
+        {
+            LOG.ignore(e);
+        }
+        finally
+        {
+            _closedOut=true;
+        }
+    }
+
+    public void shutdown()
+    {
+        final WebSocket.Connection connection = _connection;
+        if (connection != null)
+            connection.close(CLOSE_SHUTDOWN, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fillBuffersFrom(Buffer buffer)
+    {
+        _parser.fill(buffer);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void checkWriteable()
+    {
+        if (!_generator.isBufferEmpty() && _endp instanceof AsyncEndPoint)
+        {
+            ((AsyncEndPoint)_endp).scheduleWrite();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public List<Extension> getExtensions()
+    {
+        return Collections.emptyList();
+    }
+
+    protected void onFrameHandshake()
+    {
+        if (_onFrame!=null)
+        {
+            _onFrame.onHandshake(_connection);
+        }
+    }
+
+    protected void onWebSocketOpen()
+    {
+        _webSocket.onOpen(_connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    private class FrameConnectionD06 implements WebSocket.FrameConnection
+    {
+        volatile boolean _disconnecting;
+        int _maxTextMessage=WebSocketConnectionD06.this._maxTextMessageSize;
+        int _maxBinaryMessage=WebSocketConnectionD06.this._maxBinaryMessageSize;
+
+        /* ------------------------------------------------------------ */
+        public synchronized void sendMessage(String content) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closing");
+            byte[] data = content.getBytes(StringUtil.__UTF8);
+            _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_TEXT,data,0,data.length);
+            _generator.flush();
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public synchronized void sendMessage(byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closing");
+            _generator.addFrame((byte)0x8,WebSocketConnectionD06.OP_BINARY,content,offset,length);
+            _generator.flush();
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closing");
+            _generator.addFrame(flags,opcode,content,offset,length);
+            _generator.flush();
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendControl(byte control, byte[] data, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closing");
+            _generator.addFrame((byte)0x8,control,data,offset,length);
+            _generator.flush();
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isMessageComplete(byte flags)
+        {
+            return isLastFrame(flags);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isOpen()
+        {
+            return _endp!=null&&_endp.isOpen();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close(int code, String message)
+        {
+            if (_disconnecting)
+                return;
+            _disconnecting=true;
+            WebSocketConnectionD06.this.closeOut(code,message);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxIdleTime(int ms)
+        {
+            try
+            {
+                _endp.setMaxIdleTime(ms);
+            }
+            catch(IOException e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxTextMessageSize(int size)
+        {
+            _maxTextMessage=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxBinaryMessageSize(int size)
+        {
+            _maxBinaryMessage=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxTextMessageSize()
+        {
+            return _maxTextMessage;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxIdleTime()
+        {
+            return _endp.getMaxIdleTime();
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxBinaryMessageSize()
+        {
+            return _maxBinaryMessage;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getProtocol()
+        {
+            return _protocol;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte binaryOpcode()
+        {
+            return OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte textOpcode()
+        {
+            return OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte continuationOpcode()
+        {
+            return OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte finMask()
+        {
+            return 0x8;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isControl(byte opcode)
+        {
+            return isControlFrame(opcode);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isText(byte opcode)
+        {
+            return opcode==OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isBinary(byte opcode)
+        {
+            return opcode==OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isContinuation(byte opcode)
+        {
+            return opcode==OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isClose(byte opcode)
+        {
+            return opcode==OP_CLOSE;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPing(byte opcode)
+        {
+            return opcode==OP_PING;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPong(byte opcode)
+        {
+            return opcode==OP_PONG;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void disconnect()
+        {
+            close();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close()
+        {
+            close(CLOSE_NORMAL,null);
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
+        }
+
+        public void setAllowFrameFragmentation(boolean allowFragmentation)
+        {
+        }
+
+        public boolean isAllowFrameFragmentation()
+        {
+            return false;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class FrameHandlerD06 implements WebSocketParser.FrameHandler
+    {
+        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
+        private ByteArrayBuffer _aggregate;
+        private byte _opcode=-1;
+
+        public void onFrame(byte flags, byte opcode, Buffer buffer)
+        {
+            boolean lastFrame = isLastFrame(flags);
+
+            synchronized(WebSocketConnectionD06.this)
+            {
+                // Ignore incoming after a close
+                if (_closedIn)
+                    return;
+
+                try
+                {
+                    byte[] array=buffer.array();
+
+                    // Deliver frame if websocket is a FrameWebSocket
+                    if (_onFrame!=null)
+                    {
+                        if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
+                            return;
+                    }
+
+                    if (_onControl!=null && isControlFrame(opcode))
+                    {
+                        if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
+                            return;
+                    }
+
+                    switch(opcode)
+                    {
+                        case WebSocketConnectionD06.OP_CONTINUATION:
+                        {
+                            // If text, append to the message buffer
+                            if (_opcode==WebSocketConnectionD06.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
+                            {
+                                if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                                {
+                                    // If this is the last fragment, deliver the text buffer
+                                    if (lastFrame && _onTextMessage!=null)
+                                    {
+                                        _opcode=-1;
+                                        String msg =_utf8.toString();
+                                        _utf8.reset();
+                                        _onTextMessage.onMessage(msg);
+                                    }
+                                }
+                                else
+                                {
+                                    _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
+                                    _utf8.reset();
+                                    _opcode=-1;
+                                }
+                            }
+                            else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
+                            {
+                                if (_aggregate.space()<_aggregate.length())
+                                {
+                                    _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
+                                    _aggregate.clear();
+                                    _opcode=-1;
+                                }
+                                else
+                                {
+                                    _aggregate.put(buffer);
+
+                                    // If this is the last fragment, deliver
+                                    if (lastFrame && _onBinaryMessage!=null)
+                                    {
+                                        try
+                                        {
+                                            _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
+                                        }
+                                        finally
+                                        {
+                                            _opcode=-1;
+                                            _aggregate.clear();
+                                        }
+                                    }
+                                }
+                            }
+                            break;
+                        }
+                        case WebSocketConnectionD06.OP_PING:
+                        {
+                            LOG.debug("PING {}",this);
+                            if (!_closedOut)
+                                _connection.sendControl(WebSocketConnectionD06.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
+                            break;
+                        }
+
+                        case WebSocketConnectionD06.OP_PONG:
+                        {
+                            LOG.debug("PONG {}",this);
+                            break;
+                        }
+
+                        case WebSocketConnectionD06.OP_CLOSE:
+                        {
+                            int code=-1;
+                            String message=null;
+                            if (buffer.length()>=2)
+                            {
+                                code=buffer.array()[buffer.getIndex()]*0xff+buffer.array()[buffer.getIndex()+1];
+                                if (buffer.length()>2)
+                                    message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
+                            }
+                            closeIn(code,message);
+                            break;
+                        }
+
+
+                        case WebSocketConnectionD06.OP_TEXT:
+                        {
+                            if(_onTextMessage!=null)
+                            {
+                                if (lastFrame)
+                                {
+                                    // Deliver the message
+                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
+                                }
+                                else
+                                {
+                                    if (_connection.getMaxTextMessageSize()>=0)
+                                    {
+                                        // If this is a text fragment, append to buffer
+                                        if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                                            _opcode=WebSocketConnectionD06.OP_TEXT;
+                                        else
+                                        {
+                                            _utf8.reset();
+                                            _opcode=-1;
+                                            _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
+                                        }
+                                    }
+                                }
+                            }
+                            break;
+                        }
+
+                        default:
+                        {
+                            if (_onBinaryMessage!=null)
+                            {
+                                if (lastFrame)
+                                {
+                                    _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
+                                }
+                                else
+                                {
+                                    if (_connection.getMaxBinaryMessageSize()>=0)
+                                    {
+                                        if (buffer.length()>_connection.getMaxBinaryMessageSize())
+                                        {
+                                            _connection.close(WebSocketConnectionD06.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
+                                            if (_aggregate!=null)
+                                                _aggregate.clear();
+                                            _opcode=-1;
+                                        }
+                                        else
+                                        {
+                                            _opcode=opcode;
+                                            if (_aggregate==null)
+                                                _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
+                                            _aggregate.put(buffer);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                catch(Throwable th)
+                {
+                    LOG.warn(th);
+                }
+            }
+        }
+
+        public void close(int code,String message)
+        {
+            _connection.close(code,message);
+        }
+
+        @Override
+        public String toString()
+        {
+            return WebSocketConnectionD06.this.toString()+"FH";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String hashKey(String key)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            md.update(key.getBytes("UTF-8"));
+            md.update(MAGIC);
+            return new String(B64Code.encode(md.digest()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
new file mode 100644
index 0000000..a6fa9ff
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
@@ -0,0 +1,854 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
+import org.eclipse.jetty.websocket.WebSocket.OnControl;
+import org.eclipse.jetty.websocket.WebSocket.OnFrame;
+import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
+
+public class WebSocketConnectionD08 extends AbstractConnection implements WebSocketConnection
+{
+    private static final Logger LOG = Log.getLogger(WebSocketConnectionD08.class);
+
+    final static byte OP_CONTINUATION = 0x00;
+    final static byte OP_TEXT = 0x01;
+    final static byte OP_BINARY = 0x02;
+    final static byte OP_EXT_DATA = 0x03;
+
+    final static byte OP_CONTROL = 0x08;
+    final static byte OP_CLOSE = 0x08;
+    final static byte OP_PING = 0x09;
+    final static byte OP_PONG = 0x0A;
+    final static byte OP_EXT_CTRL = 0x0B;
+
+    final static int CLOSE_NORMAL=1000;
+    final static int CLOSE_SHUTDOWN=1001;
+    final static int CLOSE_PROTOCOL=1002;
+    final static int CLOSE_BADDATA=1003;
+    final static int CLOSE_NOCODE=1005;
+    final static int CLOSE_NOCLOSE=1006;
+    final static int CLOSE_NOTUTF8=1007;
+
+    final static int FLAG_FIN=0x8;
+
+    final static int VERSION=8;
+
+    static boolean isLastFrame(byte flags)
+    {
+        return (flags&FLAG_FIN)!=0;
+    }
+
+    static boolean isControlFrame(byte opcode)
+    {
+        return (opcode&OP_CONTROL)!=0;
+    }
+
+    private final static byte[] MAGIC;
+    private final List<Extension> _extensions;
+    private final WebSocketParserD08 _parser;
+    private final WebSocketGeneratorD08 _generator;
+    private final WebSocketGenerator _outbound;
+    private final WebSocket _webSocket;
+    private final OnFrame _onFrame;
+    private final OnBinaryMessage _onBinaryMessage;
+    private final OnTextMessage _onTextMessage;
+    private final OnControl _onControl;
+    private final String _protocol;
+    private final int _draft;
+    private final ClassLoader _context;
+    private volatile int _closeCode;
+    private volatile String _closeMessage;
+    private volatile boolean _closedIn;
+    private volatile boolean _closedOut;
+    private int _maxTextMessageSize=-1;
+    private int _maxBinaryMessageSize=-1;
+
+    static
+    {
+        try
+        {
+            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final WebSocket.FrameConnection _connection = new WSFrameConnection();
+
+
+    /* ------------------------------------------------------------ */
+    public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
+        throws IOException
+    {
+        this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebSocketConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
+        throws IOException
+    {
+        super(endpoint,timestamp);
+
+        _context=Thread.currentThread().getContextClassLoader();
+
+        _draft=draft;
+        _endp.setMaxIdleTime(maxIdleTime);
+
+        _webSocket = websocket;
+        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
+        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
+        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
+        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
+        _generator = new WebSocketGeneratorD08(buffers, _endp,maskgen);
+
+        _extensions=extensions;
+        WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
+        if (_extensions!=null)
+        {
+            int e=0;
+            for (Extension extension : _extensions)
+            {
+                extension.bind(
+                        _connection,
+                        e==extensions.size()-1?_frameHandler:extensions.get(e+1),
+                        e==0?_generator:extensions.get(e-1));
+                e++;
+            }
+        }
+
+        _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
+        WebSocketParser.FrameHandler _inbound=(_extensions==null||_extensions.size()==0)?_frameHandler:extensions.get(0);
+
+        _parser = new WebSocketParserD08(buffers, endpoint,_inbound,maskgen==null);
+
+        _protocol=protocol;
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebSocket.Connection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    public List<Extension> getExtensions()
+    {
+        if (_extensions==null)
+            return Collections.emptyList();
+
+        return _extensions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection handle() throws IOException
+    {
+        Thread current = Thread.currentThread();
+        ClassLoader oldcontext = current.getContextClassLoader();
+        current.setContextClassLoader(_context);
+        try
+        {
+            // handle the framing protocol
+            boolean progress=true;
+
+            while (progress)
+            {
+                int flushed=_generator.flushBuffer();
+                int filled=_parser.parseNext();
+
+                progress = flushed>0 || filled>0;
+                _endp.flush();
+
+                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
+                    progress=true;
+            }
+        }
+        catch(IOException e)
+        {
+            try
+            {
+                if (_endp.isOpen())
+                    _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+            throw e;
+        }
+        finally
+        {
+            current.setContextClassLoader(oldcontext);
+            _parser.returnBuffer();
+            _generator.returnBuffer();
+            if (_endp.isOpen())
+            {
+                if (_closedIn && _closedOut && _outbound.isBufferEmpty())
+                    _endp.close();
+                else if (_endp.isInputShutdown() && !_closedIn)
+                    closeIn(CLOSE_NOCLOSE,null);
+                else
+                    checkWriteable();
+            }
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onInputShutdown() throws IOException
+    {
+        if (!_closedIn)
+            _endp.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onIdleExpired(long idleForMs)
+    {
+        closeOut(WebSocketConnectionD08.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms");
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        final boolean closed;
+        synchronized (this)
+        {
+            closed=_closeCode==0;
+            if (closed)
+                _closeCode=WebSocketConnectionD08.CLOSE_NOCLOSE;
+        }
+        if (closed)
+            _webSocket.onClose(WebSocketConnectionD08.CLOSE_NOCLOSE,"closed");
+    }
+
+    /* ------------------------------------------------------------ */
+    public void closeIn(int code,String message)
+    {
+        LOG.debug("ClosedIn {} {} {}",this,code,message);
+
+        final boolean closed_out;
+        final boolean tell_app;
+        synchronized (this)
+        {
+            closed_out=_closedOut;
+            _closedIn=true;
+            tell_app=_closeCode==0;
+            if (tell_app)
+            {
+                _closeCode=code;
+                _closeMessage=message;
+            }
+        }
+
+        try
+        {
+            if (!closed_out)
+                closeOut(code,message);
+        }
+        finally
+        {
+            if  (tell_app)
+                _webSocket.onClose(code,message);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void closeOut(int code,String message)
+    {
+        LOG.debug("ClosedOut {} {} {}",this,code,message);
+
+        final boolean closed_out;
+        final boolean tell_app;
+        synchronized (this)
+        {
+            closed_out=_closedOut;
+            _closedOut=true;
+            tell_app=_closeCode==0;
+            if (tell_app)
+            {
+                _closeCode=code;
+                _closeMessage=message;
+            }
+        }
+
+        try
+        {                    
+            if (tell_app)
+                _webSocket.onClose(code,message);
+        }
+        finally
+        {
+            try
+            {
+                if (!closed_out)
+                {
+                    if (code<=0)
+                        code=WebSocketConnectionD08.CLOSE_NORMAL;
+                    byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
+                    bytes[0]=(byte)(code/0x100);
+                    bytes[1]=(byte)(code%0x100);
+                    _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_CLOSE,bytes,0,bytes.length);
+                }
+                _outbound.flush();
+
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+    public void shutdown()
+    {
+        final WebSocket.Connection connection = _connection;
+        if (connection != null)
+            connection.close(CLOSE_SHUTDOWN, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fillBuffersFrom(Buffer buffer)
+    {
+        _parser.fill(buffer);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void checkWriteable()
+    {
+        if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
+        {
+            ((AsyncEndPoint)_endp).scheduleWrite();
+        }
+    }
+
+    protected void onFrameHandshake()
+    {
+        if (_onFrame != null)
+        {
+            _onFrame.onHandshake(_connection);
+        }
+    }
+
+    protected void onWebSocketOpen()
+    {
+        _webSocket.onOpen(_connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    private class WSFrameConnection implements WebSocket.FrameConnection
+    {
+        private volatile boolean _disconnecting;
+
+        /* ------------------------------------------------------------ */
+        public void sendMessage(String content) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            byte[] data = content.getBytes(StringUtil.__UTF8);
+            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_TEXT,data,0,data.length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendMessage(byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD08.OP_BINARY,content,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame(flags,opcode,content,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isMessageComplete(byte flags)
+        {
+            return isLastFrame(flags);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isOpen()
+        {
+            return _endp!=null&&_endp.isOpen();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close(int code, String message)
+        {
+            if (_disconnecting)
+                return;
+            _disconnecting=true;
+            WebSocketConnectionD08.this.closeOut(code,message);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxIdleTime(int ms)
+        {
+            try
+            {
+                _endp.setMaxIdleTime(ms);
+            }
+            catch(IOException e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxTextMessageSize(int size)
+        {
+            _maxTextMessageSize=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxBinaryMessageSize(int size)
+        {
+            _maxBinaryMessageSize=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxIdleTime()
+        {
+            return _endp.getMaxIdleTime();
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxTextMessageSize()
+        {
+            return _maxTextMessageSize;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxBinaryMessageSize()
+        {
+            return _maxBinaryMessageSize;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getProtocol()
+        {
+            return _protocol;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte binaryOpcode()
+        {
+            return OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte textOpcode()
+        {
+            return OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte continuationOpcode()
+        {
+            return OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte finMask()
+        {
+            return FLAG_FIN;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isControl(byte opcode)
+        {
+            return isControlFrame(opcode);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isText(byte opcode)
+        {
+            return opcode==OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isBinary(byte opcode)
+        {
+            return opcode==OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isContinuation(byte opcode)
+        {
+            return opcode==OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isClose(byte opcode)
+        {
+            return opcode==OP_CLOSE;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPing(byte opcode)
+        {
+            return opcode==OP_PING;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPong(byte opcode)
+        {
+            return opcode==OP_PONG;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void disconnect()
+        {
+            close(CLOSE_NORMAL,null);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close()
+        {
+            close(CLOSE_NORMAL,null);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setAllowFrameFragmentation(boolean allowFragmentation)
+        {
+            _parser.setFakeFragments(allowFragmentation);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isAllowFrameFragmentation()
+        {
+            return _parser.isFakeFragments();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("%s[D08]@%x l(%s:%d)<->r(%s:%d)",
+                    getClass().getSimpleName(),
+                    hashCode(),
+                    _endp.getLocalAddr(),
+                    _endp.getLocalPort(),
+                    _endp.getRemoteAddr(),
+                    _endp.getRemotePort());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class WSFrameHandler implements WebSocketParser.FrameHandler
+    {
+        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
+        private ByteArrayBuffer _aggregate;
+        private byte _opcode=-1;
+
+        public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
+        {
+            boolean lastFrame = isLastFrame(flags);
+
+            synchronized(WebSocketConnectionD08.this)
+            {
+                // Ignore incoming after a close
+                if (_closedIn)
+                    return;
+            }
+            try
+            {
+                byte[] array=buffer.array();
+
+                // Deliver frame if websocket is a FrameWebSocket
+                if (_onFrame!=null)
+                {
+                    if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
+                        return;
+                }
+
+                if (_onControl!=null && isControlFrame(opcode))
+                {
+                    if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
+                        return;
+                }
+
+                switch(opcode)
+                {
+                    case WebSocketConnectionD08.OP_CONTINUATION:
+                    {
+                        // If text, append to the message buffer
+                        if (_onTextMessage!=null && _opcode==WebSocketConnectionD08.OP_TEXT)
+                        {
+                            if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                            {
+                                // If this is the last fragment, deliver the text buffer
+                                if (lastFrame)
+                                {
+                                    _opcode=-1;
+                                    String msg =_utf8.toString();
+                                    _utf8.reset();
+                                    _onTextMessage.onMessage(msg);
+                                }
+                            }
+                            else
+                                textMessageTooLarge();
+                        }
+
+                        if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
+                        {
+                            if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
+                            {
+                                _aggregate.put(buffer);
+
+                                // If this is the last fragment, deliver
+                                if (lastFrame && _onBinaryMessage!=null)
+                                {
+                                    try
+                                    {
+                                        _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
+                                    }
+                                    finally
+                                    {
+                                        _opcode=-1;
+                                        _aggregate.clear();
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case WebSocketConnectionD08.OP_PING:
+                    {
+                        LOG.debug("PING {}",this);
+                        if (!_closedOut)
+                            _connection.sendControl(WebSocketConnectionD08.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
+                        break;
+                    }
+
+                    case WebSocketConnectionD08.OP_PONG:
+                    {
+                        LOG.debug("PONG {}",this);
+                        break;
+                    }
+
+                    case WebSocketConnectionD08.OP_CLOSE:
+                    {
+                        int code=WebSocketConnectionD08.CLOSE_NOCODE;
+                        String message=null;
+                        if (buffer.length()>=2)
+                        {
+                            code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
+                            if (buffer.length()>2)
+                                message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
+                        }
+                        closeIn(code,message);
+                        break;
+                    }
+
+                    case WebSocketConnectionD08.OP_TEXT:
+                    {
+                        if(_onTextMessage!=null)
+                        {
+                            if (_connection.getMaxTextMessageSize()<=0)
+                            {
+                                // No size limit, so handle only final frames
+                                if (lastFrame)
+                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
+                                else
+                                {
+                                    LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
+                                    _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text frame aggregation disabled");
+                                }
+                            }
+                            // append bytes to message buffer (if they fit)
+                            else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                            {
+                                if (lastFrame)
+                                {
+                                    String msg =_utf8.toString();
+                                    _utf8.reset();
+                                    _onTextMessage.onMessage(msg);
+                                }
+                                else
+                                {
+                                    _opcode=WebSocketConnectionD08.OP_TEXT;
+                                }
+                            }
+                            else
+                                textMessageTooLarge();
+                        }
+                        break;
+                    }
+
+                    default:
+                    {
+                        if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
+                        {
+                            if (lastFrame)
+                            {
+                                _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
+                            }
+                            else if (_connection.getMaxBinaryMessageSize()>=0)
+                            {
+                                _opcode=opcode;
+                                if (_aggregate==null)
+                                    _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
+                                _aggregate.put(buffer);
+                            }
+                            else
+                            {
+                                LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
+                                _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Binary frame aggregation disabled");
+                            }
+                        }
+                    }
+                }
+            }
+            catch(Throwable e)
+            {
+                LOG.warn("{} for {}",e,_endp, e);
+                LOG.debug(e);
+                errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR,"Internal Server Error: "+e);
+            }
+        }
+
+        private void errorClose(int code, String message)
+        {
+            _connection.close(code,message);
+
+            // Brutally drop the connection
+            try
+            {
+                _endp.close();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(e.toString());
+                LOG.debug(e);
+            }
+        }
+
+        private boolean checkBinaryMessageSize(int bufferLen, int length)
+        {
+            int max = _connection.getMaxBinaryMessageSize();
+            if (max>0 && (bufferLen+length)>max)
+            {
+                LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
+                _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Message size > "+_connection.getMaxBinaryMessageSize());
+                _opcode=-1;
+                if (_aggregate!=null)
+                    _aggregate.clear();
+                return false;
+            }
+            return true;
+        }
+
+        private void textMessageTooLarge()
+        {
+            LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
+            _connection.close(WebSocketConnectionD08.CLOSE_BADDATA,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
+
+            _opcode=-1;
+            _utf8.reset();
+        }
+
+        public void close(int code,String message)
+        {
+            if (code!=CLOSE_NORMAL)
+                LOG.warn("Close: "+code+" "+message);
+            _connection.close(code,message);
+        }
+
+        @Override
+        public String toString()
+        {
+            return WebSocketConnectionD08.this.toString()+"FH";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String hashKey(String key)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            md.update(key.getBytes("UTF-8"));
+            md.update(MAGIC);
+            return new String(B64Code.encode(md.digest()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("WS/D%d p=%s g=%s", _draft, _parser, _generator);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
new file mode 100644
index 0000000..9dd2773
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
@@ -0,0 +1,974 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.io.AbstractConnection;
+import org.eclipse.jetty.io.AsyncEndPoint;
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.ByteArrayBuffer;
+import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.B64Code;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Utf8Appendable;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
+import org.eclipse.jetty.websocket.WebSocket.OnControl;
+import org.eclipse.jetty.websocket.WebSocket.OnFrame;
+import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
+
+
+/* ------------------------------------------------------------ */
+/**
+ * <pre>
+ *    0                   1                   2                   3
+ *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ *   +-+-+-+-+-------+-+-------------+-------------------------------+
+ *   |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+ *   |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
+ *   |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+ *   | |1|2|3|       |K|             |                               |
+ *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ *   |     Extended payload length continued, if payload len == 127  |
+ *   + - - - - - - - - - - - - - - - +-------------------------------+
+ *   |                               |Masking-key, if MASK set to 1  |
+ *   +-------------------------------+-------------------------------+
+ *   | Masking-key (continued)       |          Payload Data         |
+ *   +-------------------------------- - - - - - - - - - - - - - - - +
+ *   :                     Payload Data continued ...                :
+ *   + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ *   |                     Payload Data continued ...                |
+ *   +---------------------------------------------------------------+
+ * </pre>
+ */
+public class WebSocketConnectionRFC6455 extends AbstractConnection implements WebSocketConnection
+{
+    private static final Logger LOG = Log.getLogger(WebSocketConnectionRFC6455.class);
+
+    final static byte OP_CONTINUATION = 0x00;
+    final static byte OP_TEXT = 0x01;
+    final static byte OP_BINARY = 0x02;
+    final static byte OP_EXT_DATA = 0x03;
+
+    final static byte OP_CONTROL = 0x08;
+    final static byte OP_CLOSE = 0x08;
+    final static byte OP_PING = 0x09;
+    final static byte OP_PONG = 0x0A;
+    final static byte OP_EXT_CTRL = 0x0B;
+
+    final static int CLOSE_NORMAL=1000;
+    final static int CLOSE_SHUTDOWN=1001;
+    final static int CLOSE_PROTOCOL=1002;
+    final static int CLOSE_BAD_DATA=1003;
+    final static int CLOSE_UNDEFINED=1004;
+    final static int CLOSE_NO_CODE=1005;
+    final static int CLOSE_NO_CLOSE=1006;
+    final static int CLOSE_BAD_PAYLOAD=1007;
+    final static int CLOSE_POLICY_VIOLATION=1008;
+    final static int CLOSE_MESSAGE_TOO_LARGE=1009;
+    final static int CLOSE_REQUIRED_EXTENSION=1010;
+    final static int CLOSE_SERVER_ERROR=1011;
+    final static int CLOSE_FAILED_TLS_HANDSHAKE=1015;
+
+    final static int FLAG_FIN=0x8;
+
+    // Per RFC 6455, section 1.3 - Opening Handshake - this version is "13"
+    final static int VERSION=13;
+
+    static boolean isLastFrame(byte flags)
+    {
+        return (flags&FLAG_FIN)!=0;
+    }
+
+    static boolean isControlFrame(byte opcode)
+    {
+        return (opcode&OP_CONTROL)!=0;
+    }
+
+    private final static byte[] MAGIC;
+    private final List<Extension> _extensions;
+    private final WebSocketParserRFC6455 _parser;
+    private final WebSocketGeneratorRFC6455 _generator;
+    private final WebSocketGenerator _outbound;
+    private final WebSocket _webSocket;
+    private final OnFrame _onFrame;
+    private final OnBinaryMessage _onBinaryMessage;
+    private final OnTextMessage _onTextMessage;
+    private final OnControl _onControl;
+    private final String _protocol;
+    private final int _draft;
+    private final ClassLoader _context;
+    private volatile int _closeCode;
+    private volatile String _closeMessage;
+    private volatile boolean _closedIn;
+    private volatile boolean _closedOut;
+    private int _maxTextMessageSize=-1;
+    private int _maxBinaryMessageSize=-1;
+
+    static
+    {
+        try
+        {
+            MAGIC="258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(StringUtil.__ISO_8859_1);
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private final WebSocket.FrameConnection _connection = new WSFrameConnection();
+
+
+    /* ------------------------------------------------------------ */
+    public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft)
+        throws IOException
+    {
+        this(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebSocketConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol, List<Extension> extensions,int draft, MaskGen maskgen)
+        throws IOException
+    {
+        super(endpoint,timestamp);
+
+        _context=Thread.currentThread().getContextClassLoader();
+
+        _draft=draft;
+        _endp.setMaxIdleTime(maxIdleTime);
+
+        _webSocket = websocket;
+        _onFrame=_webSocket instanceof OnFrame ? (OnFrame)_webSocket : null;
+        _onTextMessage=_webSocket instanceof OnTextMessage ? (OnTextMessage)_webSocket : null;
+        _onBinaryMessage=_webSocket instanceof OnBinaryMessage ? (OnBinaryMessage)_webSocket : null;
+        _onControl=_webSocket instanceof OnControl ? (OnControl)_webSocket : null;
+        _generator = new WebSocketGeneratorRFC6455(buffers, _endp,maskgen);
+
+        _extensions=extensions;
+        WebSocketParser.FrameHandler frameHandler = new WSFrameHandler();
+        if (_extensions!=null)
+        {
+            int e=0;
+            for (Extension extension : _extensions)
+            {
+                extension.bind(
+                        _connection,
+                        e==extensions.size()-1? frameHandler :extensions.get(e+1),
+                        e==0?_generator:extensions.get(e-1));
+                e++;
+            }
+        }
+
+        _outbound=(_extensions==null||_extensions.size()==0)?_generator:extensions.get(extensions.size()-1);
+        WebSocketParser.FrameHandler inbound = (_extensions == null || _extensions.size() == 0) ? frameHandler : extensions.get(0);
+
+        _parser = new WebSocketParserRFC6455(buffers, endpoint, inbound,maskgen==null);
+
+        _protocol=protocol;
+
+    }
+
+    /* ------------------------------------------------------------ */
+    public WebSocket.Connection getConnection()
+    {
+        return _connection;
+    }
+
+    /* ------------------------------------------------------------ */
+    public List<Extension> getExtensions()
+    {
+        if (_extensions==null)
+            return Collections.emptyList();
+
+        return _extensions;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Connection handle() throws IOException
+    {
+        Thread current = Thread.currentThread();
+        ClassLoader oldcontext = current.getContextClassLoader();
+        current.setContextClassLoader(_context);
+        try
+        {
+            // handle the framing protocol
+            boolean progress=true;
+
+            while (progress)
+            {
+                int flushed=_generator.flushBuffer();
+                int filled=_parser.parseNext();
+
+                progress = flushed>0 || filled>0;
+                _endp.flush();
+
+                if (_endp instanceof AsyncEndPoint && ((AsyncEndPoint)_endp).hasProgressed())
+                    progress=true;
+            }
+        }
+        catch(IOException e)
+        {
+            try
+            {
+                if (_endp.isOpen())
+                    _endp.close();
+            }
+            catch(IOException e2)
+            {
+                LOG.ignore(e2);
+            }
+            throw e;
+        }
+        finally
+        {
+            current.setContextClassLoader(oldcontext);
+            _parser.returnBuffer();
+            _generator.returnBuffer();
+            if (_endp.isOpen())
+            {
+                if (_closedIn && _closedOut && _outbound.isBufferEmpty())
+                    _endp.close();
+                else if (_endp.isInputShutdown() && !_closedIn)
+                    closeIn(CLOSE_NO_CLOSE,null);
+                else
+                    checkWriteable();
+            }
+        }
+        return this;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onInputShutdown() throws IOException
+    {
+        if (!_closedIn)
+            _endp.close();
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isIdle()
+    {
+        return _parser.isBufferEmpty() && _outbound.isBufferEmpty();
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void onIdleExpired(long idleForMs)
+    {
+        closeOut(WebSocketConnectionRFC6455.CLOSE_NORMAL,"Idle for "+idleForMs+"ms > "+_endp.getMaxIdleTime()+"ms");
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isSuspended()
+    {
+        return false;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void onClose()
+    {
+        final boolean closed;
+        synchronized (this)
+        {
+            closed=_closeCode==0;
+            if (closed)
+                _closeCode=WebSocketConnectionRFC6455.CLOSE_NO_CLOSE;
+        }
+        if (closed)
+            _webSocket.onClose(WebSocketConnectionRFC6455.CLOSE_NO_CLOSE,"closed");
+    }
+
+    /* ------------------------------------------------------------ */
+    public void closeIn(int code,String message)
+    {
+        LOG.debug("ClosedIn {} {} {}",this,code,message);
+
+        final boolean closed_out;
+        final boolean tell_app;
+        synchronized (this)
+        {
+            closed_out=_closedOut;
+            _closedIn=true;
+            tell_app=_closeCode==0;
+            if (tell_app)
+            {
+                _closeCode=code;
+                _closeMessage=message;
+            }
+        }
+
+        try
+        {
+            if (!closed_out)
+                closeOut(code,message);
+        }
+        finally
+        {
+            if  (tell_app)
+                _webSocket.onClose(code,message);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void closeOut(int code,String message)
+    {
+        LOG.debug("ClosedOut {} {} {}",this,code,message);
+
+        final boolean closed_out;
+        final boolean tell_app;
+        synchronized (this)
+        {
+            closed_out=_closedOut;
+            _closedOut=true;
+            tell_app=_closeCode==0;
+            if (tell_app)
+            {
+                _closeCode=code;
+                _closeMessage=message;
+            }
+        }
+
+        try
+        {                    
+            if (tell_app)
+                _webSocket.onClose(code,message);
+        }
+        finally
+        {
+            try
+            {
+                if (!closed_out)
+                {
+                    // Close code 1005/1006/1015 are never to be sent as a status over
+                    // a Close control frame. Code<-1 also means no node.
+
+                    if (code < 0 || (code == WebSocketConnectionRFC6455.CLOSE_NO_CODE) || (code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE)
+                            || (code == WebSocketConnectionRFC6455.CLOSE_FAILED_TLS_HANDSHAKE))
+                    {
+                        code = -1;
+                    }
+                    else if (code == 0)
+                    {
+                        code = WebSocketConnectionRFC6455.CLOSE_NORMAL;
+                    }
+
+                    byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
+                    bytes[0]=(byte)(code/0x100);
+                    bytes[1]=(byte)(code%0x100);
+                    _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_CLOSE,bytes,0,code>0?bytes.length:0);
+                    _outbound.flush();
+                }
+            }
+            catch(IOException e)
+            {
+                LOG.ignore(e);
+            }
+        }
+    }
+
+    public void shutdown()
+    {
+        final WebSocket.Connection connection = _connection;
+        if (connection != null)
+            connection.close(CLOSE_SHUTDOWN, null);
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fillBuffersFrom(Buffer buffer)
+    {
+        _parser.fill(buffer);
+    }
+
+    /* ------------------------------------------------------------ */
+    private void checkWriteable()
+    {
+        if (!_outbound.isBufferEmpty() && _endp instanceof AsyncEndPoint)
+        {
+            ((AsyncEndPoint)_endp).scheduleWrite();
+        }
+    }
+
+    protected void onFrameHandshake()
+    {
+        if (_onFrame != null)
+        {
+            _onFrame.onHandshake(_connection);
+        }
+    }
+
+    protected void onWebSocketOpen()
+    {
+        _webSocket.onOpen(_connection);
+    }
+
+    /* ------------------------------------------------------------ */
+    private class WSFrameConnection implements WebSocket.FrameConnection
+    {
+        private volatile boolean _disconnecting;
+
+        /* ------------------------------------------------------------ */
+        public void sendMessage(String content) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            byte[] data = content.getBytes(StringUtil.__UTF8);
+            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_TEXT,data,0,data.length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendMessage(byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionRFC6455.OP_BINARY,content,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException
+        {
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame(flags,opcode,content,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
+        {
+            // TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented
+            if (_closedOut)
+                throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
+            _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
+            checkWriteable();
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isMessageComplete(byte flags)
+        {
+            return isLastFrame(flags);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isOpen()
+        {
+            return _endp!=null&&_endp.isOpen();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close(int code, String message)
+        {
+            if (_disconnecting)
+                return;
+            _disconnecting=true;
+            WebSocketConnectionRFC6455.this.closeOut(code,message);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxIdleTime(int ms)
+        {
+            try
+            {
+                _endp.setMaxIdleTime(ms);
+            }
+            catch(IOException e)
+            {
+                LOG.warn(e);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxTextMessageSize(int size)
+        {
+            _maxTextMessageSize=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setMaxBinaryMessageSize(int size)
+        {
+            _maxBinaryMessageSize=size;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxIdleTime()
+        {
+            return _endp.getMaxIdleTime();
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxTextMessageSize()
+        {
+            return _maxTextMessageSize;
+        }
+
+        /* ------------------------------------------------------------ */
+        public int getMaxBinaryMessageSize()
+        {
+            return _maxBinaryMessageSize;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getProtocol()
+        {
+            return _protocol;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte binaryOpcode()
+        {
+            return OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte textOpcode()
+        {
+            return OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte continuationOpcode()
+        {
+            return OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public byte finMask()
+        {
+            return FLAG_FIN;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isControl(byte opcode)
+        {
+            return isControlFrame(opcode);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isText(byte opcode)
+        {
+            return opcode==OP_TEXT;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isBinary(byte opcode)
+        {
+            return opcode==OP_BINARY;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isContinuation(byte opcode)
+        {
+            return opcode==OP_CONTINUATION;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isClose(byte opcode)
+        {
+            return opcode==OP_CLOSE;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPing(byte opcode)
+        {
+            return opcode==OP_PING;
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isPong(byte opcode)
+        {
+            return opcode==OP_PONG;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void disconnect()
+        {
+            close(CLOSE_NORMAL,null);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void close()
+        {
+            close(CLOSE_NORMAL,null);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void setAllowFrameFragmentation(boolean allowFragmentation)
+        {
+            _parser.setFakeFragments(allowFragmentation);
+        }
+
+        /* ------------------------------------------------------------ */
+        public boolean isAllowFrameFragmentation()
+        {
+            return _parser.isFakeFragments();
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public String toString()
+        {
+            return String.format("%s@%x l(%s:%d)<->r(%s:%d)",
+                    getClass().getSimpleName(),
+                    hashCode(),
+                    _endp.getLocalAddr(),
+                    _endp.getLocalPort(),
+                    _endp.getRemoteAddr(),
+                    _endp.getRemotePort());
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class WSFrameHandler implements WebSocketParser.FrameHandler
+    {
+        private static final int MAX_CONTROL_FRAME_PAYLOAD = 125;
+        private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity
+        private ByteArrayBuffer _aggregate;
+        private byte _opcode=-1;
+
+        public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
+        {
+            boolean lastFrame = isLastFrame(flags);
+
+            synchronized(WebSocketConnectionRFC6455.this)
+            {
+                // Ignore incoming after a close
+                if (_closedIn)
+                    return;
+            }
+            try
+            {
+                byte[] array=buffer.array();
+
+                if (isControlFrame(opcode) && buffer.length()>MAX_CONTROL_FRAME_PAYLOAD)
+                {
+                    errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Control frame too large: " + buffer.length() + " > " + MAX_CONTROL_FRAME_PAYLOAD);
+                    return;
+                }
+
+                // TODO: check extensions for RSV bit(s) meanings
+                if ((flags&0x7)!=0)
+                {
+                    errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags));
+                    return;
+                }
+
+                // Ignore all frames after error close
+                if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
+                {
+                    return;
+                }
+
+                // Deliver frame if websocket is a FrameWebSocket
+                if (_onFrame!=null)
+                {
+                    if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
+                        return;
+                }
+
+                if (_onControl!=null && isControlFrame(opcode))
+                {
+                    if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
+                        return;
+                }
+
+                switch(opcode)
+                {
+                    case WebSocketConnectionRFC6455.OP_CONTINUATION:
+                    {
+                        if (_opcode==-1)
+                        {
+                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad Continuation");
+                            return;
+                        }
+
+                        // If text, append to the message buffer
+                        if (_onTextMessage!=null && _opcode==WebSocketConnectionRFC6455.OP_TEXT)
+                        {
+                            if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                            {
+                                // If this is the last fragment, deliver the text buffer
+                                if (lastFrame)
+                                {
+                                    _opcode=-1;
+                                    String msg =_utf8.toString();
+                                    _utf8.reset();
+                                    _onTextMessage.onMessage(msg);
+                                }
+                            }
+                            else
+                                textMessageTooLarge();
+                        }
+
+                        if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
+                        {
+                            if (_aggregate!=null && checkBinaryMessageSize(_aggregate.length(),buffer.length()))
+                            {
+                                _aggregate.put(buffer);
+
+                                // If this is the last fragment, deliver
+                                if (lastFrame && _onBinaryMessage!=null)
+                                {
+                                    try
+                                    {
+                                        _onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
+                                    }
+                                    finally
+                                    {
+                                        _opcode=-1;
+                                        _aggregate.clear();
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+                    case WebSocketConnectionRFC6455.OP_PING:
+                    {
+                        LOG.debug("PING {}",this);
+                        if (!_closedOut)
+                        {
+                            _connection.sendControl(WebSocketConnectionRFC6455.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
+                        }
+                        break;
+                    }
+
+                    case WebSocketConnectionRFC6455.OP_PONG:
+                    {
+                        LOG.debug("PONG {}",this);
+                        break;
+                    }
+
+                    case WebSocketConnectionRFC6455.OP_CLOSE:
+                    {
+                        int code=WebSocketConnectionRFC6455.CLOSE_NO_CODE;
+                        String message=null;
+                        if (buffer.length()>=2)
+                        {
+                            code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]);
+
+                            // Validate close status codes.
+                            if (code < WebSocketConnectionRFC6455.CLOSE_NORMAL ||
+                                code == WebSocketConnectionRFC6455.CLOSE_UNDEFINED ||
+                                code == WebSocketConnectionRFC6455.CLOSE_NO_CLOSE ||
+                                code == WebSocketConnectionRFC6455.CLOSE_NO_CODE ||
+                                ( code > 1011 && code <= 2999 ) ||
+                                code >= 5000 )
+                            {
+                                errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid close code " + code);
+                                return;
+                            }
+
+                            if (buffer.length()>2)
+                            {
+                                if(_utf8.append(buffer.array(),buffer.getIndex()+2,buffer.length()-2,_connection.getMaxTextMessageSize()))
+                                {
+                                    message = _utf8.toString();
+                                    _utf8.reset();
+                                }
+                            }
+                        }
+                        else if(buffer.length() == 1)
+                        {
+                            // Invalid length. use status code 1002 (Protocol error)
+                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Invalid payload length of 1");
+                            return;
+                        }
+                        closeIn(code,message);
+                        break;
+                    }
+
+                    case WebSocketConnectionRFC6455.OP_TEXT:
+                    {
+                        if (_opcode!=-1)
+                        {
+                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
+                            return;
+                        }
+
+                        if(_onTextMessage!=null)
+                        {
+                            if (_connection.getMaxTextMessageSize()<=0)
+                            {
+                                // No size limit, so handle only final frames
+                                if (lastFrame)
+                                    _onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
+                                else
+                                {
+                                    LOG.warn("Frame discarded. Text aggregation disabled for {}",_endp);
+                                    errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Text frame aggregation disabled");
+                                }
+                            }
+                            // append bytes to message buffer (if they fit)
+                            else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
+                            {
+                                if (lastFrame)
+                                {
+                                    String msg =_utf8.toString();
+                                    _utf8.reset();
+                                    _onTextMessage.onMessage(msg);
+                                }
+                                else
+                                {
+                                    _opcode=WebSocketConnectionRFC6455.OP_TEXT;
+                                }
+                            }
+                            else
+                                textMessageTooLarge();
+                        }
+                        break;
+                    }
+
+                    case WebSocketConnectionRFC6455.OP_BINARY:
+                    {
+                        if (_opcode!=-1)
+                        {
+                            errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
+                            return;
+                        }
+
+                        if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
+                        {
+                            if (lastFrame)
+                            {
+                                _onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
+                            }
+                            else if (_connection.getMaxBinaryMessageSize()>=0)
+                            {
+                                _opcode=opcode;
+                                // TODO use a growing buffer rather than a fixed one.
+                                if (_aggregate==null)
+                                    _aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
+                                _aggregate.put(buffer);
+                            }
+                            else
+                            {
+                                LOG.warn("Frame discarded. Binary aggregation disabed for {}",_endp);
+                                errorClose(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"Binary frame aggregation disabled");
+                            }
+                        }
+                        break;
+                    }
+
+                    default:
+                        errorClose(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
+                        break;
+                }
+            }
+            catch(Utf8Appendable.NotUtf8Exception notUtf8)
+            {
+                LOG.warn("NOTUTF8 - {} for {}",notUtf8,_endp, notUtf8);
+                LOG.debug(notUtf8);
+                errorClose(WebSocketConnectionRFC6455.CLOSE_BAD_PAYLOAD,"Invalid UTF-8");
+            }
+            catch(Throwable e)
+            {
+                LOG.warn("{} for {}",e,_endp, e);
+                LOG.debug(e);
+                errorClose(WebSocketConnectionRFC6455.CLOSE_SERVER_ERROR,"Internal Server Error: "+e);
+            }
+        }
+
+        private void errorClose(int code, String message)
+        {
+            _connection.close(code,message);
+
+            // Brutally drop the connection
+            try
+            {
+                _endp.close();
+            }
+            catch (IOException e)
+            {
+                LOG.warn(e.toString());
+                LOG.debug(e);
+            }
+        }
+
+        private boolean checkBinaryMessageSize(int bufferLen, int length)
+        {
+            int max = _connection.getMaxBinaryMessageSize();
+            if (max>0 && (bufferLen+length)>max)
+            {
+                LOG.warn("Binary message too large > {}B for {}",_connection.getMaxBinaryMessageSize(),_endp);
+                _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
+                _opcode=-1;
+                if (_aggregate!=null)
+                    _aggregate.clear();
+                return false;
+            }
+            return true;
+        }
+
+        private void textMessageTooLarge()
+        {
+            LOG.warn("Text message too large > {} chars for {}",_connection.getMaxTextMessageSize(),_endp);
+            _connection.close(WebSocketConnectionRFC6455.CLOSE_MESSAGE_TOO_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
+
+            _opcode=-1;
+            _utf8.reset();
+        }
+
+        public void close(int code,String message)
+        {
+            if (code!=CLOSE_NORMAL)
+                LOG.warn("Close: "+code+" "+message);
+            _connection.close(code,message);
+        }
+
+        @Override
+        public String toString()
+        {
+            return WebSocketConnectionRFC6455.this.toString()+"FH";
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public static String hashKey(String key)
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA1");
+            md.update(key.getBytes("UTF-8"));
+            md.update(MAGIC);
+            return new String(B64Code.encode(md.digest()));
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s p=%s g=%s", getClass().getSimpleName(), _parser, _generator);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/src/java/org/eclipse/jetty/websocket/WebSocketFactory.java
new file mode 100644
index 0000000..04ab4f3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketFactory.java
@@ -0,0 +1,465 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpException;
+import org.eclipse.jetty.http.HttpParser;
+import org.eclipse.jetty.io.ConnectedEndPoint;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.BlockingHttpConnection;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Factory to create WebSocket connections
+ */
+public class WebSocketFactory extends AbstractLifeCycle
+{
+    private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
+    private final Queue<WebSocketServletConnection> connections = new ConcurrentLinkedQueue<WebSocketServletConnection>();
+
+    public interface Acceptor
+    {
+        /* ------------------------------------------------------------ */
+        /**
+         * <p>Factory method that applications needs to implement to return a
+         * {@link WebSocket} object.</p>
+         * @param request the incoming HTTP upgrade request
+         * @param protocol the websocket sub protocol
+         * @return a new {@link WebSocket} object that will handle websocket events.
+         */
+        WebSocket doWebSocketConnect(HttpServletRequest request, String protocol);
+
+        /* ------------------------------------------------------------ */
+        /**
+         * <p>Checks the origin of an incoming WebSocket handshake request.</p>
+         * @param request the incoming HTTP upgrade request
+         * @param origin the origin URI
+         * @return boolean to indicate that the origin is acceptable.
+         */
+        boolean checkOrigin(HttpServletRequest request, String origin);
+    }
+
+    private final Map<String,Class<? extends Extension>> _extensionClasses = new HashMap<String, Class<? extends Extension>>();
+    {
+        _extensionClasses.put("identity",IdentityExtension.class);
+        _extensionClasses.put("fragment",FragmentExtension.class);
+        _extensionClasses.put("x-deflate-frame",DeflateFrameExtension.class);
+    }
+
+    private final Acceptor _acceptor;
+    private WebSocketBuffers _buffers;
+    private int _maxIdleTime = 300000;
+    private int _maxTextMessageSize = 16 * 1024;
+    private int _maxBinaryMessageSize = -1;
+    private int _minVersion;
+
+    public WebSocketFactory(Acceptor acceptor)
+    {
+        this(acceptor, 64 * 1024, WebSocketConnectionRFC6455.VERSION);
+    }
+
+    public WebSocketFactory(Acceptor acceptor, int bufferSize)
+    {
+        this(acceptor, bufferSize, WebSocketConnectionRFC6455.VERSION);
+    }
+
+    public WebSocketFactory(Acceptor acceptor, int bufferSize, int minVersion)
+    {
+        _buffers = new WebSocketBuffers(bufferSize);
+        _acceptor = acceptor;
+        _minVersion=WebSocketConnectionRFC6455.VERSION;
+    }
+
+    public int getMinVersion()
+    {
+        return _minVersion;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param minVersion The minimum support version (default RCF6455.VERSION == 13 )
+     */
+    public void setMinVersion(int minVersion)
+    {
+        _minVersion = minVersion;
+    }
+
+    /**
+     * @return A modifiable map of extension name to extension class
+     */
+    public Map<String,Class<? extends Extension>> getExtensionClassesMap()
+    {
+        return _extensionClasses;
+    }
+
+    /**
+     * Get the maxIdleTime.
+     *
+     * @return the maxIdleTime
+     */
+    public long getMaxIdleTime()
+    {
+        return _maxIdleTime;
+    }
+
+    /**
+     * Set the maxIdleTime.
+     *
+     * @param maxIdleTime the maxIdleTime to set
+     */
+    public void setMaxIdleTime(int maxIdleTime)
+    {
+        _maxIdleTime = maxIdleTime;
+    }
+
+    /**
+     * Get the bufferSize.
+     *
+     * @return the bufferSize
+     */
+    public int getBufferSize()
+    {
+        return _buffers.getBufferSize();
+    }
+
+    /**
+     * Set the bufferSize.
+     *
+     * @param bufferSize the bufferSize to set
+     */
+    public void setBufferSize(int bufferSize)
+    {
+        if (bufferSize != getBufferSize())
+            _buffers = new WebSocketBuffers(bufferSize);
+    }
+
+    /**
+     * @return The initial maximum text message size (in characters) for a connection
+     */
+    public int getMaxTextMessageSize()
+    {
+        return _maxTextMessageSize;
+    }
+
+    /**
+     * Set the initial maximum text message size for a connection. This can be changed by
+     * the application calling {@link WebSocket.Connection#setMaxTextMessageSize(int)}.
+     * @param maxTextMessageSize The default maximum text message size (in characters) for a connection
+     */
+    public void setMaxTextMessageSize(int maxTextMessageSize)
+    {
+        _maxTextMessageSize = maxTextMessageSize;
+    }
+
+    /**
+     * @return The initial maximum binary message size (in bytes)  for a connection
+     */
+    public int getMaxBinaryMessageSize()
+    {
+        return _maxBinaryMessageSize;
+    }
+
+    /**
+     * Set the initial maximum binary message size for a connection. This can be changed by
+     * the application calling {@link WebSocket.Connection#setMaxBinaryMessageSize(int)}.
+     * @param maxBinaryMessageSize The default maximum binary message size (in bytes) for a connection
+     */
+    public void setMaxBinaryMessageSize(int maxBinaryMessageSize)
+    {
+        _maxBinaryMessageSize = maxBinaryMessageSize;
+    }
+
+    @Override
+    protected void doStop() throws Exception
+    {
+        closeConnections();
+    }
+
+    /**
+     * Upgrade the request/response to a WebSocket Connection.
+     * <p>This method will not normally return, but will instead throw a
+     * UpgradeConnectionException, to exit HTTP handling and initiate
+     * WebSocket handling of the connection.
+     *
+     * @param request   The request to upgrade
+     * @param response  The response to upgrade
+     * @param websocket The websocket handler implementation to use
+     * @param protocol  The websocket protocol
+     * @throws IOException in case of I/O errors
+     */
+    public void upgrade(HttpServletRequest request, HttpServletResponse response, WebSocket websocket, String protocol)
+            throws IOException
+    {
+        if (!"websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
+            throw new IllegalStateException("!Upgrade:websocket");
+        if (!"HTTP/1.1".equals(request.getProtocol()))
+            throw new IllegalStateException("!HTTP/1.1");
+
+        int draft = request.getIntHeader("Sec-WebSocket-Version");
+        if (draft < 0) {
+            // Old pre-RFC version specifications (header not present in RFC-6455)
+            draft = request.getIntHeader("Sec-WebSocket-Draft");
+        }
+        // Remember requested version for possible error message later
+        int requestedVersion = draft;
+        AbstractHttpConnection http = AbstractHttpConnection.getCurrentConnection();
+        if (http instanceof BlockingHttpConnection)
+            throw new IllegalStateException("Websockets not supported on blocking connectors");
+        ConnectedEndPoint endp = (ConnectedEndPoint)http.getEndPoint();
+
+        List<String> extensions_requested = new ArrayList<String>();
+        @SuppressWarnings("unchecked")
+        Enumeration<String> e = request.getHeaders("Sec-WebSocket-Extensions");
+        while (e.hasMoreElements())
+        {
+            QuotedStringTokenizer tok = new QuotedStringTokenizer(e.nextElement(),",");
+            while (tok.hasMoreTokens())
+            {
+                extensions_requested.add(tok.nextToken());
+            }
+        }
+
+        final WebSocketServletConnection connection;
+        if (draft<_minVersion)
+            draft=Integer.MAX_VALUE;
+        switch (draft)
+        {
+            case -1: // unspecified draft/version (such as early OSX Safari 5.1 and iOS 5.x)
+            case 0: // Old school draft/version
+            {
+                connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
+                break;
+            }
+            case 1:
+            case 2:
+            case 3:
+            case 4:
+            case 5:
+            case 6:
+            {
+                connection = new WebSocketServletConnectionD06(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
+                break;
+            }
+            case 7:
+            case 8:
+            {
+                List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionD08.OP_EXT_DATA, 16 - WebSocketConnectionD08.OP_EXT_CTRL, 3);
+                connection = new WebSocketServletConnectionD08(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
+                break;
+            }
+            case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version
+            {
+                List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3);
+                connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
+                break;
+            }
+            default:
+            {
+                // Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
+                // Using the examples as outlined
+                String versions="13";
+                if (_minVersion<=8)
+                    versions+=", 8";
+                if (_minVersion<=6)
+                    versions+=", 6";
+                if (_minVersion<=0)
+                    versions+=", 0";
+                    
+                response.setHeader("Sec-WebSocket-Version", versions);
+
+                // Make error clear for developer / end-user
+                StringBuilder err = new StringBuilder();
+                err.append("Unsupported websocket client version specification ");
+                if(requestedVersion >= 0) {
+                    err.append("[").append(requestedVersion).append("]");
+                } else {
+                    err.append("<Unspecified, likely a pre-draft version of websocket>");
+                }
+                err.append(", configured minVersion [").append(_minVersion).append("]");
+                err.append(", reported supported versions [").append(versions).append("]");
+                LOG.warn(err.toString()); // Log it
+                // use spec language for unsupported versions
+                throw new HttpException(400, "Unsupported websocket version specification"); // Tell client
+            }
+        }
+
+        addConnection(connection);
+
+        // Set the defaults
+        connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
+        connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
+
+        // Let the connection finish processing the handshake
+        connection.handshake(request, response, protocol);
+        response.flushBuffer();
+
+        // Give the connection any unused data from the HTTP connection.
+        connection.fillBuffersFrom(((HttpParser)http.getParser()).getHeaderBuffer());
+        connection.fillBuffersFrom(((HttpParser)http.getParser()).getBodyBuffer());
+
+        // Tell jetty about the new connection
+        LOG.debug("Websocket upgrade {} {} {} {}",request.getRequestURI(),draft,protocol,connection);
+        request.setAttribute("org.eclipse.jetty.io.Connection", connection);
+    }
+
+    protected String[] parseProtocols(String protocol)
+    {
+        if (protocol == null)
+            return new String[]{null};
+        protocol = protocol.trim();
+        if (protocol == null || protocol.length() == 0)
+            return new String[]{null};
+        String[] passed = protocol.split("\\s*,\\s*");
+        String[] protocols = new String[passed.length + 1];
+        System.arraycopy(passed, 0, protocols, 0, passed.length);
+        return protocols;
+    }
+
+    public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
+            throws IOException
+    {
+        if ("websocket".equalsIgnoreCase(request.getHeader("Upgrade")))
+        {
+            String origin = request.getHeader("Origin");
+            if (origin==null)
+                origin = request.getHeader("Sec-WebSocket-Origin");
+            if (!_acceptor.checkOrigin(request,origin))
+            {
+                response.sendError(HttpServletResponse.SC_FORBIDDEN);
+                return false;
+            }
+
+            // Try each requested protocol
+            WebSocket websocket = null;
+
+            @SuppressWarnings("unchecked")
+            Enumeration<String> protocols = request.getHeaders("Sec-WebSocket-Protocol");
+            String protocol=null;
+            while (protocol==null && protocols!=null && protocols.hasMoreElements())
+            {
+                String candidate = protocols.nextElement();
+                for (String p : parseProtocols(candidate))
+                {
+                    websocket = _acceptor.doWebSocketConnect(request, p);
+                    if (websocket != null)
+                    {
+                        protocol = p;
+                        break;
+                    }
+                }
+            }
+
+            // Did we get a websocket?
+            if (websocket == null)
+            {
+                // Try with no protocol
+                websocket = _acceptor.doWebSocketConnect(request, null);
+
+                if (websocket==null)
+                {
+                    response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                    return false;
+                }
+            }
+
+            // Send the upgrade
+            upgrade(request, response, websocket, protocol);
+            return true;
+        }
+
+        return false;
+    }
+
+    public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
+    {
+        List<Extension> extensions = new ArrayList<Extension>();
+        for (String rExt : requested)
+        {
+            QuotedStringTokenizer tok = new QuotedStringTokenizer(rExt,";");
+            String extName=tok.nextToken().trim();
+            Map<String,String> parameters = new HashMap<String,String>();
+            while (tok.hasMoreTokens())
+            {
+                QuotedStringTokenizer nv = new QuotedStringTokenizer(tok.nextToken().trim(),"=");
+                String name=nv.nextToken().trim();
+                String value=nv.hasMoreTokens()?nv.nextToken().trim():null;
+                parameters.put(name,value);
+            }
+
+            Extension extension = newExtension(extName);
+
+            if (extension==null)
+                continue;
+
+            if (extension.init(parameters))
+            {
+                LOG.debug("add {} {}",extName,parameters);
+                extensions.add(extension);
+            }
+        }
+        LOG.debug("extensions={}",extensions);
+        return extensions;
+    }
+
+    private Extension newExtension(String name)
+    {
+        try
+        {
+            Class<? extends Extension> extClass = _extensionClasses.get(name);
+            if (extClass!=null)
+                return extClass.newInstance();
+        }
+        catch (Exception e)
+        {
+            LOG.warn(e);
+        }
+
+        return null;
+    }
+
+    protected boolean addConnection(WebSocketServletConnection connection)
+    {
+        return isRunning() && connections.add(connection);
+    }
+
+    protected boolean removeConnection(WebSocketServletConnection connection)
+    {
+        return connections.remove(connection);
+    }
+
+    protected void closeConnections()
+    {
+        for (WebSocketServletConnection connection : connections)
+            connection.shutdown();
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketGenerator.java b/src/java/org/eclipse/jetty/websocket/WebSocketGenerator.java
new file mode 100644
index 0000000..fb69e1a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketGenerator.java
@@ -0,0 +1,33 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+
+
+/* ------------------------------------------------------------ */
+/** WebSocketGenerator.
+ */
+public interface WebSocketGenerator
+{
+    int flush() throws IOException;
+    boolean isBufferEmpty();
+    void addFrame(byte flags,byte opcode, byte[] content, int offset, int length) throws IOException;
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java
new file mode 100644
index 0000000..502b0b3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD00.java
@@ -0,0 +1,173 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+
+
+/* ------------------------------------------------------------ */
+/** WebSocketGenerator.
+ * This class generates websocket packets.
+ * It is fully synchronized because it is likely that async
+ * threads will call the addMessage methods while other
+ * threads are flushing the generator.
+ */
+public class WebSocketGeneratorD00 implements WebSocketGenerator
+{
+    final private WebSocketBuffers _buffers;
+    final private EndPoint _endp;
+    private Buffer _buffer;
+
+    public WebSocketGeneratorD00(WebSocketBuffers buffers, EndPoint endp)
+    {
+        _buffers=buffers;
+        _endp=endp;
+    }
+    
+    public synchronized void addFrame(byte flags, byte opcode,byte[] content, int offset, int length) throws IOException
+    {
+        long blockFor=_endp.getMaxIdleTime();
+        
+        if (_buffer==null)
+            _buffer=_buffers.getDirectBuffer();
+
+        if (_buffer.space() == 0)
+            expelBuffer(blockFor);
+
+        bufferPut(opcode, blockFor);
+
+        if (isLengthFrame(opcode))
+        {
+            // Send a length delimited frame
+
+            // How many bytes we need for the length ?
+            // We have 7 bits available, so log2(length) / 7 + 1
+            // For example, 50000 bytes is 2 8-bytes: 11000011 01010000
+            // but we need to write it in 3 7-bytes 0000011 0000110 1010000
+            // 65536 == 1 00000000 00000000 => 100 0000000 0000000
+            int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
+            for (int i = lengthBytes - 1; i > 0; --i)
+            {
+                byte lengthByte = (byte)(0x80 | (0x7F & (length >> 7 * i)));
+                bufferPut(lengthByte, blockFor);
+            }
+            bufferPut((byte)(0x7F & length), blockFor);
+        }
+
+        int remaining = length;
+        while (remaining > 0)
+        {
+            int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
+            _buffer.put(content, offset + (length - remaining), chunk);
+            remaining -= chunk;
+            if (_buffer.space() > 0)
+            {
+                if (!isLengthFrame(opcode))
+                    _buffer.put((byte)0xFF);
+                // Gently flush the data, issuing a non-blocking write
+                flushBuffer();
+            }
+            else
+            {
+                // Forcibly flush the data, issuing a blocking write
+                expelBuffer(blockFor);
+                if (remaining == 0)
+                {
+                    if (!isLengthFrame(opcode))
+                        _buffer.put((byte)0xFF);
+                    // Gently flush the data, issuing a non-blocking write
+                    flushBuffer();
+                }
+            }
+        }
+    }
+
+    private synchronized boolean isLengthFrame(byte frame)
+    {
+        return (frame & WebSocketConnectionD00.LENGTH_FRAME) == WebSocketConnectionD00.LENGTH_FRAME;
+    }
+
+    private synchronized void bufferPut(byte datum, long blockFor) throws IOException
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getDirectBuffer();
+        _buffer.put(datum);
+        if (_buffer.space() == 0)
+            expelBuffer(blockFor);
+    }
+
+    public synchronized int flush(int blockFor) throws IOException
+    {
+        return expelBuffer(blockFor);
+    }
+
+    public synchronized int flush() throws IOException
+    {
+        int flushed = flushBuffer();
+        if (_buffer!=null && _buffer.length()==0)
+        {
+            _buffers.returnBuffer(_buffer);
+            _buffer=null;
+        }
+        return flushed;
+    }
+
+    private synchronized int flushBuffer() throws IOException
+    {
+        if (!_endp.isOpen())
+            throw new EofException();
+
+        if (_buffer!=null && _buffer.hasContent())
+            return _endp.flush(_buffer);
+
+        return 0;
+    }
+
+    private synchronized int expelBuffer(long blockFor) throws IOException
+    {
+        if (_buffer==null)
+            return 0;
+        int result = flushBuffer();
+        _buffer.compact();
+        if (!_endp.isBlocking())
+        {
+            while (_buffer.space()==0)
+            {
+                boolean ready = _endp.blockWritable(blockFor);
+                if (!ready)
+                    throw new IOException("Write timeout");
+
+                result += flushBuffer();
+                _buffer.compact();
+            }
+        }
+        return result;
+    }
+
+    public synchronized boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java
new file mode 100644
index 0000000..129b1c6
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD06.java
@@ -0,0 +1,234 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+
+
+/* ------------------------------------------------------------ */
+/** WebSocketGenerator.
+ * This class generates websocket packets.
+ * It is fully synchronized because it is likely that async
+ * threads will call the addMessage methods while other
+ * threads are flushing the generator.
+ */
+public class WebSocketGeneratorD06 implements WebSocketGenerator
+{
+    final private WebSocketBuffers _buffers;
+    final private EndPoint _endp;
+    private Buffer _buffer;
+    private final byte[] _mask=new byte[4];
+    private int _m;
+    private boolean _opsent;
+    private final MaskGen _maskGen;
+
+    public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _maskGen=null;
+    }
+
+    public WebSocketGeneratorD06(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _maskGen=maskGen;
+    }
+
+    public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length);
+
+        long blockFor=_endp.getMaxIdleTime();
+
+        if (_buffer==null)
+            _buffer=(_maskGen!=null)?_buffers.getBuffer():_buffers.getDirectBuffer();
+
+        boolean last=WebSocketConnectionD06.isLastFrame(flags);
+        opcode=(byte)(((0xf&flags)<<4)+0xf&opcode);
+
+        int space=(_maskGen!=null)?14:10;
+
+        do
+        {
+            opcode = _opsent?WebSocketConnectionD06.OP_CONTINUATION:opcode;
+            _opsent=true;
+
+            int payload=length;
+            if (payload+space>_buffer.capacity())
+            {
+                // We must fragement, so clear FIN bit
+                opcode&=(byte)0x7F; // Clear the FIN bit
+                payload=_buffer.capacity()-space;
+            }
+            else if (last)
+                opcode|=(byte)0x80; // Set the FIN bit
+
+            // ensure there is space for header
+            if (_buffer.space() <= space)
+                expelBuffer(blockFor);
+
+            // write mask
+            if ((_maskGen!=null))
+            {
+                _maskGen.genMask(_mask);
+                _m=0;
+                _buffer.put(_mask);
+            }
+
+            // write the opcode and length
+            if (payload>0xffff)
+            {
+                bufferPut(new byte[]{
+                        opcode,
+                        (byte)0x7f,
+                        (byte)0,
+                        (byte)0,
+                        (byte)0,
+                        (byte)0,
+                        (byte)((payload>>24)&0xff),
+                        (byte)((payload>>16)&0xff),
+                        (byte)((payload>>8)&0xff),
+                        (byte)(payload&0xff)});
+            }
+            else if (payload >=0x7e)
+            {
+                bufferPut(new byte[]{
+                        opcode,
+                        (byte)0x7e,
+                        (byte)(payload>>8),
+                        (byte)(payload&0xff)});
+            }
+            else
+            {
+                bufferPut(opcode);
+                bufferPut((byte)payload);
+            }
+
+            // write payload
+            int remaining = payload;
+            while (remaining > 0)
+            {
+                _buffer.compact();
+                int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
+
+                if ((_maskGen!=null))
+                {
+                    for (int i=0;i<chunk;i++)
+                        bufferPut(content[offset+ (payload-remaining)+i]);
+                }
+                else
+                    _buffer.put(content, offset + (payload - remaining), chunk);
+
+                remaining -= chunk;
+                if (_buffer.space() > 0)
+                {
+                    // Gently flush the data, issuing a non-blocking write
+                    flushBuffer();
+                }
+                else
+                {
+                    // Forcibly flush the data, issuing a blocking write
+                    expelBuffer(blockFor);
+                    if (remaining == 0)
+                    {
+                        // Gently flush the data, issuing a non-blocking write
+                        flushBuffer();
+                    }
+                }
+            }
+            offset+=payload;
+            length-=payload;
+        }
+        while (length>0);
+        _opsent=!last;
+    }
+
+    private synchronized void bufferPut(byte[] data) throws IOException
+    {
+        if (_maskGen!=null)
+            for (int i=0;i<data.length;i++)
+                data[i]^=_mask[+_m++%4];
+        _buffer.put(data);
+    }
+
+    private synchronized void bufferPut(byte data) throws IOException
+    {
+        _buffer.put((byte)(data^_mask[+_m++%4]));
+    }
+
+    public synchronized int flush(int blockFor) throws IOException
+    {
+        return expelBuffer(blockFor);
+    }
+
+    public synchronized int flush() throws IOException
+    {
+        int flushed = flushBuffer();
+        if (_buffer!=null && _buffer.length()==0)
+        {
+            _buffers.returnBuffer(_buffer);
+            _buffer=null;
+        }
+        return flushed;
+    }
+
+    private synchronized int flushBuffer() throws IOException
+    {
+        if (!_endp.isOpen())
+            throw new EofException();
+
+        if (_buffer!=null)
+            return _endp.flush(_buffer);
+
+        return 0;
+    }
+
+    private synchronized int expelBuffer(long blockFor) throws IOException
+    {
+        if (_buffer==null)
+            return 0;
+        int result = flushBuffer();
+        _buffer.compact();
+        if (!_endp.isBlocking())
+        {
+            while (_buffer.space()==0)
+            {
+                boolean ready = _endp.blockWritable(blockFor);
+                if (!ready)
+                    throw new IOException("Write timeout");
+
+                result += flushBuffer();
+                _buffer.compact();
+            }
+        }
+        return result;
+    }
+
+    public synchronized boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java
new file mode 100644
index 0000000..d2bb753
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorD08.java
@@ -0,0 +1,308 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+
+/**
+ * WebSocketGenerator. This class generates websocket packets. It is fully synchronized because it is likely that async threads will call the addMessage methods
+ * while other threads are flushing the generator.
+ */
+public class WebSocketGeneratorD08 implements WebSocketGenerator
+{
+    private final Lock _lock = new ReentrantLock();
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final byte[] _mask = new byte[4];
+    private final MaskGen _maskGen;
+    private Buffer _buffer;
+    private int _m;
+    private boolean _opsent;
+    private boolean _closed;
+
+    public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp)
+    {
+        this(buffers, endp, null);
+    }
+
+    public WebSocketGeneratorD08(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
+    {
+        _buffers = buffers;
+        _endp = endp;
+        _maskGen = maskGen;
+    }
+
+    public Buffer getBuffer()
+    {
+        _lock.lock();
+        try
+        {
+            return _buffer;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        _lock.lock();
+        try
+        {
+            if (_closed)
+                throw new EofException("Closed");
+            if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
+                _closed = true;
+
+            boolean mask = _maskGen != null;
+
+            if (_buffer == null)
+                _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
+
+            boolean last = WebSocketConnectionD08.isLastFrame(flags);
+
+            int space = mask ? 14 : 10;
+
+            do
+            {
+                opcode = _opsent ? WebSocketConnectionD08.OP_CONTINUATION : opcode;
+                opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
+                _opsent = true;
+
+                int payload = length;
+                if (payload + space > _buffer.capacity())
+                {
+                    // We must fragement, so clear FIN bit
+                    opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
+                    payload = _buffer.capacity() - space;
+                }
+                else if (last)
+                    opcode = (byte)(opcode | 0x80); // Set the FIN bit
+
+                // ensure there is space for header
+                if (_buffer.space() <= space)
+                {
+                    flushBuffer();
+                    if (_buffer.space() <= space)
+                        flush();
+                }
+
+                // write the opcode and length
+                if (payload > 0xffff)
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            mask ? (byte)0xff : (byte)0x7f,
+                            (byte)0,
+                            (byte)0,
+                            (byte)0,
+                            (byte)0,
+                            (byte)((payload >> 24) & 0xff),
+                            (byte)((payload >> 16) & 0xff),
+                            (byte)((payload >> 8) & 0xff),
+                            (byte)(payload & 0xff)});
+                }
+                else if (payload >= 0x7e)
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            mask ? (byte)0xfe : (byte)0x7e,
+                            (byte)(payload >> 8),
+                            (byte)(payload & 0xff)});
+                }
+                else
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            (byte)(mask ? (0x80 | payload) : payload)});
+                }
+
+                // write mask
+                if (mask)
+                {
+                    _maskGen.genMask(_mask);
+                    _m = 0;
+                    _buffer.put(_mask);
+                }
+
+                // write payload
+                int remaining = payload;
+                while (remaining > 0)
+                {
+                    _buffer.compact();
+                    int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
+
+                    if (mask)
+                    {
+                        for (int i = 0; i < chunk; i++)
+                            _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
+                    }
+                    else
+                        _buffer.put(content, offset + (payload - remaining), chunk);
+
+                    remaining -= chunk;
+                    if (_buffer.space() > 0)
+                    {
+                        // Gently flush the data, issuing a non-blocking write
+                        flushBuffer();
+                    }
+                    else
+                    {
+                        // Forcibly flush the data, issuing a blocking write
+                        flush();
+                        if (remaining == 0)
+                        {
+                            // Gently flush the data, issuing a non-blocking write
+                            flushBuffer();
+                        }
+                    }
+                }
+                offset += payload;
+                length -= payload;
+            }
+            while (length > 0);
+            _opsent = !last;
+
+            if (_buffer != null && _buffer.length() == 0)
+            {
+                _buffers.returnBuffer(_buffer);
+                _buffer = null;
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public int flushBuffer() throws IOException
+    {
+        if (!_lock.tryLock())
+            return 0;
+
+        try
+        {
+            if (!_endp.isOpen())
+                throw new EofException();
+
+            if (_buffer != null)
+            {
+                int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
+                if (_closed && _buffer.length() == 0)
+                    _endp.shutdownOutput();
+                return flushed;
+            }
+
+            return 0;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public int flush() throws IOException
+    {
+        if (!_lock.tryLock())
+            return 0;
+
+        try
+        {
+            if (_buffer == null)
+                return 0;
+
+            int result = flushBuffer();
+            if (!_endp.isBlocking())
+            {
+                long now = System.currentTimeMillis();
+                long end = now + _endp.getMaxIdleTime();
+                while (_buffer.length() > 0)
+                {
+                    boolean ready = _endp.blockWritable(end - now);
+                    if (!ready)
+                    {
+                        now = System.currentTimeMillis();
+                        if (now < end)
+                            continue;
+                        throw new IOException("Write timeout");
+                    }
+
+                    result += flushBuffer();
+                }
+            }
+            _buffer.compact();
+            return result;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public boolean isBufferEmpty()
+    {
+        _lock.lock();
+        try
+        {
+            return _buffer == null || _buffer.length() == 0;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public void returnBuffer()
+    {
+        _lock.lock();
+        try
+        {
+            if (_buffer != null && _buffer.length() == 0)
+            {
+                _buffers.returnBuffer(_buffer);
+                _buffer = null;
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        Buffer buffer = _buffer;
+        return String.format("%s@%x closed=%b buffer=%d",
+                getClass().getSimpleName(),
+                hashCode(),
+                _closed,
+                buffer == null ? -1 : buffer.length());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
new file mode 100644
index 0000000..5a539ca
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java
@@ -0,0 +1,312 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.EofException;
+
+
+/**
+ * WebSocketGenerator.
+ * This class generates websocket packets.
+ * It is fully synchronized because it is likely that async
+ * threads will call the addMessage methods while other
+ * threads are flushing the generator.
+ */
+public class WebSocketGeneratorRFC6455 implements WebSocketGenerator
+{
+    private final Lock _lock = new ReentrantLock();
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final byte[] _mask = new byte[4];
+    private final MaskGen _maskGen;
+    private Buffer _buffer;
+    private int _m;
+    private boolean _opsent;
+    private boolean _closed;
+
+    public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp)
+    {
+        this(buffers, endp, null);
+    }
+
+    public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen)
+    {
+        _buffers = buffers;
+        _endp = endp;
+        _maskGen = maskGen;
+    }
+
+    public Buffer getBuffer()
+    {
+        _lock.lock();
+        try
+        {
+            return _buffer;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException
+    {
+        _lock.lock();
+        try
+        {
+            if (_closed)
+                throw new EofException("Closed");
+            if (opcode == WebSocketConnectionRFC6455.OP_CLOSE)
+                _closed = true;
+
+            boolean mask = _maskGen != null;
+
+            if (_buffer == null)
+                _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer();
+
+            boolean last = WebSocketConnectionRFC6455.isLastFrame(flags);
+
+            int space = mask ? 14 : 10;
+
+            do
+            {
+                opcode = _opsent ? WebSocketConnectionRFC6455.OP_CONTINUATION : opcode;
+                opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode));
+                _opsent = true;
+
+                int payload = length;
+                if (payload + space > _buffer.capacity())
+                {
+                    // We must fragement, so clear FIN bit
+                    opcode = (byte)(opcode & 0x7F); // Clear the FIN bit
+                    payload = _buffer.capacity() - space;
+                }
+                else if (last)
+                    opcode = (byte)(opcode | 0x80); // Set the FIN bit
+
+                // ensure there is space for header
+                if (_buffer.space() <= space)
+                {
+                    flushBuffer();
+                    if (_buffer.space() <= space)
+                        flush();
+                }
+
+                // write the opcode and length
+                if (payload > 0xffff)
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            mask ? (byte)0xff : (byte)0x7f,
+                            (byte)0,
+                            (byte)0,
+                            (byte)0,
+                            (byte)0,
+                            (byte)((payload >> 24) & 0xff),
+                            (byte)((payload >> 16) & 0xff),
+                            (byte)((payload >> 8) & 0xff),
+                            (byte)(payload & 0xff)});
+                }
+                else if (payload >= 0x7e)
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            mask ? (byte)0xfe : (byte)0x7e,
+                            (byte)(payload >> 8),
+                            (byte)(payload & 0xff)});
+                }
+                else
+                {
+                    _buffer.put(new byte[]{
+                            opcode,
+                            (byte)(mask ? (0x80 | payload) : payload)});
+                }
+
+                // write mask
+                if (mask)
+                {
+                    _maskGen.genMask(_mask);
+                    _m = 0;
+                    _buffer.put(_mask);
+                }
+
+                // write payload
+                int remaining = payload;
+                while (remaining > 0)
+                {
+                    _buffer.compact();
+                    int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
+
+                    if (mask)
+                    {
+                        for (int i = 0; i < chunk; i++)
+                            _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4]));
+                    }
+                    else
+                        _buffer.put(content, offset + (payload - remaining), chunk);
+
+                    remaining -= chunk;
+                    if (_buffer.space() > 0)
+                    {
+                        // Gently flush the data, issuing a non-blocking write
+                        flushBuffer();
+                    }
+                    else
+                    {
+                        // Forcibly flush the data, issuing a blocking write
+                        flush();
+                        if (remaining == 0)
+                        {
+                            // Gently flush the data, issuing a non-blocking write
+                            flushBuffer();
+                        }
+                    }
+                }
+                offset += payload;
+                length -= payload;
+            }
+            while (length > 0);
+            _opsent = !last;
+
+            if (_buffer != null && _buffer.length() == 0)
+            {
+                _buffers.returnBuffer(_buffer);
+                _buffer = null;
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public int flushBuffer() throws IOException
+    {
+        if (!_lock.tryLock())
+            return 0;
+
+        try
+        {
+            if (!_endp.isOpen())
+                throw new EofException();
+
+            if (_buffer != null)
+            {
+                int flushed = _buffer.hasContent() ? _endp.flush(_buffer) : 0;
+                if (_closed && _buffer.length() == 0)
+                    _endp.shutdownOutput();
+                return flushed;
+            }
+
+            return 0;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public int flush() throws IOException
+    {
+        if (!_lock.tryLock())
+            return 0;
+
+        try
+        {
+            if (_buffer == null)
+                return 0;
+
+            int result = flushBuffer();
+            if (!_endp.isBlocking())
+            {
+                long now = System.currentTimeMillis();
+                long end = now + _endp.getMaxIdleTime();
+                while (_buffer.length() > 0)
+                {
+                    boolean ready = _endp.blockWritable(end - now);
+                    if (!ready)
+                    {
+                        now = System.currentTimeMillis();
+                        if (now < end)
+                            continue;
+                        throw new IOException("Write timeout");
+                    }
+
+                    result += flushBuffer();
+                }
+            }
+            _buffer.compact();
+            return result;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public boolean isBufferEmpty()
+    {
+        _lock.lock();
+        try
+        {
+            return _buffer == null || _buffer.length() == 0;
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    public void returnBuffer()
+    {
+        _lock.lock();
+        try
+        {
+            if (_buffer != null && _buffer.length() == 0)
+            {
+                _buffers.returnBuffer(_buffer);
+                _buffer = null;
+            }
+        }
+        finally
+        {
+            _lock.unlock();
+        }
+    }
+
+    @Override
+    public String toString()
+    {
+        // Do NOT use synchronized (this)
+        // because it's very easy to deadlock when debugging is enabled.
+        // We do a best effort to print the right toString() and that's it.
+        Buffer buffer = _buffer;
+        return String.format("%s@%x closed=%b buffer=%d",
+                getClass().getSimpleName(),
+                hashCode(),
+                _closed,
+                buffer == null ? -1 : buffer.length());
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketHandler.java b/src/java/org/eclipse/jetty/websocket/WebSocketHandler.java
new file mode 100644
index 0000000..5d5f9e4
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketHandler.java
@@ -0,0 +1,57 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+
+public abstract class WebSocketHandler extends HandlerWrapper implements WebSocketFactory.Acceptor
+{
+    private final WebSocketFactory _webSocketFactory=new WebSocketFactory(this,32*1024);
+    
+    public WebSocketFactory getWebSocketFactory()
+    {
+        return _webSocketFactory;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+    {
+        if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
+        {
+            baseRequest.setHandled(true);
+            return;
+        }
+        super.handle(target,baseRequest,request,response);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public boolean checkOrigin(HttpServletRequest request, String origin)
+    {
+        return true;
+    }
+    
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketParser.java b/src/java/org/eclipse/jetty/websocket/WebSocketParser.java
new file mode 100644
index 0000000..87a48a3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketParser.java
@@ -0,0 +1,53 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import org.eclipse.jetty.io.Buffer;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public interface WebSocketParser
+{
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    public interface FrameHandler
+    {
+        void onFrame(byte flags, byte opcode, Buffer buffer);
+        void close(int code,String message);
+    }
+
+    Buffer getBuffer();
+
+    /**
+     * @return an indication of progress, normally bytes filled plus events parsed, or -1 for EOF
+     */
+    int parseNext();
+
+    boolean isBufferEmpty();
+
+    void fill(Buffer buffer);
+
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketParserD00.java b/src/java/org/eclipse/jetty/websocket/WebSocketParserD00.java
new file mode 100644
index 0000000..93cc355
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketParserD00.java
@@ -0,0 +1,212 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public class WebSocketParserD00 implements WebSocketParser
+{
+    private static final Logger LOG = Log.getLogger(WebSocketParserD00.class);
+
+    public static final int STATE_START=0;
+    public static final int STATE_SENTINEL_DATA=1;
+    public static final int STATE_LENGTH=2;
+    public static final int STATE_DATA=3;
+
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final FrameHandler _handler;
+    private int _state;
+    private Buffer _buffer;
+    private byte _opcode;
+    private int _length;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
+     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
+     * is mostly used.
+     * @param endp the endpoint
+     * @param handler the handler to notify when a parse event occurs
+     */
+    public WebSocketParserD00(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _handler=handler;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        return _buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse to next event.
+     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
+     * available. Fill data from the {@link EndPoint} only as necessary.
+     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
+     * that no bytes were read and no messages parsed. A positive number indicates either
+     * the bytes filled or the messages parsed.
+     */
+    public int parseNext()
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+
+        int progress=0;
+
+        // Loop until an datagram call back or can't fill anymore
+        while(true)
+        {
+            int length=_buffer.length();
+
+            // Fill buffer if we need a byte or need length
+            if (length == 0 || _state==STATE_DATA && length<_length)
+            {
+                // compact to mark (set at start of data)
+                _buffer.compact();
+
+                // if no space, then the data is too big for buffer
+                if (_buffer.space() == 0)
+                    throw new IllegalStateException("FULL");
+
+                // catch IOExceptions (probably EOF) and try to parse what we have
+                try
+                {
+                    int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
+                    if (filled<=0)
+                        return progress;
+                    progress+=filled;
+                    length=_buffer.length();
+                }
+                catch(IOException e)
+                {
+                    LOG.debug(e);
+                    return progress>0?progress:-1;
+                }
+            }
+
+
+            // Parse the buffer byte by byte (unless it is STATE_DATA)
+            byte b;
+            charloop: while (length-->0)
+            {
+                switch (_state)
+                {
+                    case STATE_START:
+                        b=_buffer.get();
+                        _opcode=b;
+                        if (_opcode<0)
+                        {
+                            _length=0;
+                            _state=STATE_LENGTH;
+                        }
+                        else
+                        {
+                            _state=STATE_SENTINEL_DATA;
+                            _buffer.mark(0);
+                        }
+                        continue;
+
+                    case STATE_SENTINEL_DATA:
+                        b=_buffer.get();
+                        if ((b&0xff)==0xff)
+                        {
+                            _state=STATE_START;
+                            int l=_buffer.getIndex()-_buffer.markIndex()-1;
+                            progress++;
+                            _handler.onFrame((byte)0,_opcode,_buffer.sliceFromMark(l));
+                            _buffer.setMarkIndex(-1);
+                            if (_buffer.length()==0)
+                            {
+                                _buffers.returnBuffer(_buffer);
+                                _buffer=null;
+                            }
+                            return progress;
+                        }
+                        continue;
+
+                    case STATE_LENGTH:
+                        b=_buffer.get();
+                        _length=_length<<7 | (0x7f&b);
+                        if (b>=0)
+                        {
+                            _state=STATE_DATA;
+                            _buffer.mark(0);
+                        }
+                        continue;
+
+                    case STATE_DATA:
+                        if (_buffer.markIndex()<0)
+                        if (_buffer.length()<_length)
+                            break charloop;
+                        Buffer data=_buffer.sliceFromMark(_length);
+                        _buffer.skip(_length);
+                        _state=STATE_START;
+                        progress++;
+                        _handler.onFrame((byte)0, _opcode, data);
+
+                        if (_buffer.length()==0)
+                        {
+                            _buffers.returnBuffer(_buffer);
+                            _buffer=null;
+                        }
+
+                        return progress;
+                }
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fill(Buffer buffer)
+    {
+        if (buffer!=null && buffer.length()>0)
+        {
+            if (_buffer==null)
+                _buffer=_buffers.getBuffer();
+            _buffer.put(buffer);
+            buffer.clear();
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketParserD06.java b/src/java/org/eclipse/jetty/websocket/WebSocketParserD06.java
new file mode 100644
index 0000000..5539780
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketParserD06.java
@@ -0,0 +1,310 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public class WebSocketParserD06 implements WebSocketParser
+{
+    private static final Logger LOG = Log.getLogger(WebSocketParserD06.class);
+
+    public enum State {
+
+        START(0), MASK(4), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), DATA(0), SKIP(1);
+
+        int _needs;
+
+        State(int needs)
+        {
+            _needs=needs;
+        }
+
+        int getNeeds()
+        {
+            return _needs;
+        }
+    }
+
+
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final FrameHandler _handler;
+    private final boolean _masked;
+    private State _state;
+    private Buffer _buffer;
+    private byte _flags;
+    private byte _opcode;
+    private int _bytesNeeded;
+    private long _length;
+    private final byte[] _mask = new byte[4];
+    private int _m;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
+     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
+     * is mostly used.
+     * @param endp the endpoint
+     * @param handler the handler to notify when a parse event occurs
+     * @param masked whether masking should be handled
+     */
+    public WebSocketParserD06(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean masked)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _handler=handler;
+        _masked=masked;
+        _state=State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        return _buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse to next event.
+     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
+     * available. Fill data from the {@link EndPoint} only as necessary.
+     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
+     * that no bytes were read and no messages parsed. A positive number indicates either
+     * the bytes filled or the messages parsed.
+     */
+    public int parseNext()
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+
+        int total_filled=0;
+        int events=0;
+
+        // Loop until an datagram call back or can't fill anymore
+        while(true)
+        {
+            int available=_buffer.length();
+
+            // Fill buffer if we need a byte or need length
+            while (available<(_state==State.SKIP?1:_bytesNeeded))
+            {
+                // compact to mark (set at start of data)
+                _buffer.compact();
+
+                // if no space, then the data is too big for buffer
+                if (_buffer.space() == 0)
+                    throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
+
+                // catch IOExceptions (probably EOF) and try to parse what we have
+                try
+                {
+                    int filled=_endp.isOpen()?_endp.fill(_buffer):-1;
+                    if (filled<=0)
+                        return (total_filled+events)>0?(total_filled+events):filled;
+                    total_filled+=filled;
+                    available=_buffer.length();
+                }
+                catch(IOException e)
+                {
+                    LOG.debug(e);
+                    return (total_filled+events)>0?(total_filled+events):-1;
+                }
+            }
+
+            // if we are here, then we have sufficient bytes to process the current state.
+
+            // Parse the buffer byte by byte (unless it is STATE_DATA)
+            byte b;
+            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
+            {
+                switch (_state)
+                {
+                    case START:
+                        _state=_masked?State.MASK:State.OPCODE;
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case MASK:
+                        _buffer.get(_mask,0,4);
+                        available-=4;
+                        _state=State.OPCODE;
+                        _bytesNeeded=_state.getNeeds();
+                        _m=0;
+                        continue;
+
+                    case OPCODE:
+                        b=_buffer.get();
+                        available--;
+                        if (_masked)
+                            b^=_mask[_m++%4];
+                        _opcode=(byte)(b&0xf);
+                        _flags=(byte)(0xf&(b>>4));
+
+                        if (WebSocketConnectionD06.isControlFrame(_opcode)&&!WebSocketConnectionD06.isLastFrame(_flags))
+                        {
+                            _state=State.SKIP;
+                            events++;
+                            _handler.close(WebSocketConnectionD06.CLOSE_PROTOCOL,"fragmented control");
+                        }
+                        else
+                            _state=State.LENGTH_7;
+
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case LENGTH_7:
+                        b=_buffer.get();
+                        available--;
+                        if (_masked)
+                            b^=_mask[_m++%4];
+                        switch(b)
+                        {
+                            case 127:
+                                _length=0;
+                                _state=State.LENGTH_63;
+                                _bytesNeeded=_state.getNeeds();
+                                break;
+                            case 126:
+                                _length=0;
+                                _state=State.LENGTH_16;
+                                _bytesNeeded=_state.getNeeds();
+                                break;
+                            default:
+                                _length=(0x7f&b);
+                                _bytesNeeded=(int)_length;
+                                _state=State.DATA;
+                        }
+                        continue;
+
+                    case LENGTH_16:
+                        b=_buffer.get();
+                        available--;
+                        if (_masked)
+                            b^=_mask[_m++%4];
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            _bytesNeeded=(int)_length;
+                            if (_length>_buffer.capacity())
+                            {
+                                _state=State.SKIP;
+                                events++;
+                                _handler.close(WebSocketConnectionD06.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
+                            }
+                            else
+                            {
+                                _state=State.DATA;
+                            }
+                        }
+                        continue;
+
+                    case LENGTH_63:
+                        b=_buffer.get();
+                        available--;
+                        if (_masked)
+                            b^=_mask[_m++%4];
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            _bytesNeeded=(int)_length;
+                            if (_length>=_buffer.capacity())
+                            {
+                                _state=State.SKIP;
+                                events++;
+                                _handler.close(WebSocketConnectionD06.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
+                            }
+                            else
+                            {
+                                _state=State.DATA;
+                            }
+                        }
+                        continue;
+
+                    case SKIP:
+                        int skip=Math.min(available,_bytesNeeded);
+                        _buffer.skip(skip);
+                        available-=skip;
+                        _bytesNeeded-=skip;
+                        if (_bytesNeeded==0)
+                            _state=State.START;
+
+                }
+            }
+
+            if (_state==State.DATA && available>=_bytesNeeded)
+            {
+                Buffer data =_buffer.get(_bytesNeeded);
+                if (_masked)
+                {
+                    if (data.array()==null)
+                        data=_buffer.asMutableBuffer();
+                    byte[] array = data.array();
+                    final int end=data.putIndex();
+                    for (int i=data.getIndex();i<end;i++)
+                        array[i]^=_mask[_m++%4];
+                }
+
+                // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+                events++;
+                _handler.onFrame(_flags, _opcode, data);
+                _bytesNeeded=0;
+                _state=State.START;
+
+                if (_buffer.length()==0)
+                {
+                    _buffers.returnBuffer(_buffer);
+                    _buffer=null;
+                }
+
+                return total_filled+events;
+            }
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fill(Buffer buffer)
+    {
+        if (buffer!=null && buffer.length()>0)
+        {
+            if (_buffer==null)
+                _buffer=_buffers.getBuffer();
+            _buffer.put(buffer);
+            buffer.clear();
+        }
+    }
+
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketParserD08.java b/src/java/org/eclipse/jetty/websocket/WebSocketParserD08.java
new file mode 100644
index 0000000..362414a
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketParserD08.java
@@ -0,0 +1,394 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public class WebSocketParserD08 implements WebSocketParser
+{
+    private static final Logger LOG = Log.getLogger(WebSocketParserD08.class);
+
+    public enum State {
+
+        START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
+
+        int _needs;
+
+        State(int needs)
+        {
+            _needs=needs;
+        }
+
+        int getNeeds()
+        {
+            return _needs;
+        }
+    }
+
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final FrameHandler _handler;
+    private final boolean _shouldBeMasked;
+    private State _state;
+    private Buffer _buffer;
+    private byte _flags;
+    private byte _opcode;
+    private int _bytesNeeded;
+    private long _length;
+    private boolean _masked;
+    private final byte[] _mask = new byte[4];
+    private int _m;
+    private boolean _skip;
+    private boolean _fragmentFrames=true;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
+     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
+     * is mostly used.
+     * @param endp the endpoint
+     * @param handler the handler to notify when a parse event occurs
+     * @param shouldBeMasked whether masking should be handled
+     */
+    public WebSocketParserD08(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _handler=handler;
+        _shouldBeMasked=shouldBeMasked;
+        _state=State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if fake fragments should be created for frames larger than the buffer.
+     */
+    public boolean isFakeFragments()
+    {
+        return _fragmentFrames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
+     */
+    public void setFakeFragments(boolean fakeFragments)
+    {
+        _fragmentFrames = fakeFragments;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        return _buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse to next event.
+     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
+     * available. Fill data from the {@link EndPoint} only as necessary.
+     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
+     * that no bytes were read and no messages parsed. A positive number indicates either
+     * the bytes filled or the messages parsed.
+     */
+    public int parseNext()
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+
+        boolean progress=false;
+        int filled=-1;
+
+        // Loop until a datagram call back or can't fill anymore
+        while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
+        {
+            int available=_buffer.length();
+
+            // Fill buffer if we need a byte or need length
+            while (available<(_state==State.SKIP?1:_bytesNeeded))
+            {
+                // compact to mark (set at start of data)
+                _buffer.compact();
+
+                // if no space, then the data is too big for buffer
+                if (_buffer.space() == 0)
+                {
+                    // Can we send a fake frame?
+                    if (_fragmentFrames && _state==State.DATA)
+                    {
+                        Buffer data =_buffer.get(4*(available/4));
+                        _buffer.compact();
+                        if (_masked)
+                        {
+                            if (data.array()==null)
+                                data=_buffer.asMutableBuffer();
+                            byte[] array = data.array();
+                            final int end=data.putIndex();
+                            for (int i=data.getIndex();i<end;i++)
+                                array[i]^=_mask[_m++%4];
+                        }
+
+                        // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+                        _bytesNeeded-=data.length();
+                        progress=true;
+                        _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD08.FLAG_FIN)), _opcode, data);
+
+                        _opcode=WebSocketConnectionD08.OP_CONTINUATION;
+                    }
+
+                    if (_buffer.space() == 0)
+                        throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
+                }
+
+                // catch IOExceptions (probably EOF) and try to parse what we have
+                try
+                {
+                    filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
+                    available=_buffer.length();
+                    // System.err.printf(">> filled %d/%d%n",filled,available);
+                    if (filled<=0)
+                        break;
+                }
+                catch(IOException e)
+                {
+                    LOG.debug(e);
+                    filled=-1;
+                    break;
+                }
+            }
+            // Did we get enough?
+            if (available<(_state==State.SKIP?1:_bytesNeeded))
+                break;
+
+            // if we are here, then we have sufficient bytes to process the current state.
+            // Parse the buffer byte by byte (unless it is STATE_DATA)
+            byte b;
+            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
+            {
+                switch (_state)
+                {
+                    case START:
+                        _skip=false;
+                        _state=_opcode==WebSocketConnectionD08.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case OPCODE:
+                        b=_buffer.get();
+                        available--;
+                        _opcode=(byte)(b&0xf);
+                        _flags=(byte)(0xf&(b>>4));
+
+                        if (WebSocketConnectionD08.isControlFrame(_opcode)&&!WebSocketConnectionD08.isLastFrame(_flags))
+                        {
+                            LOG.warn("Fragmented Control from "+_endp);
+                            _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"Fragmented control");
+                            progress=true;
+                            _skip=true;
+                        }
+
+                        _state=State.LENGTH_7;
+                        _bytesNeeded=_state.getNeeds();
+
+                        continue;
+
+                    case LENGTH_7:
+                        b=_buffer.get();
+                        available--;
+                        _masked=(b&0x80)!=0;
+                        b=(byte)(0x7f&b);
+
+                        switch(b)
+                        {
+                            case 0x7f:
+                                _length=0;
+                                _state=State.LENGTH_63;
+                                break;
+                            case 0x7e:
+                                _length=0;
+                                _state=State.LENGTH_16;
+                                break;
+                            default:
+                                _length=(0x7f&b);
+                                _state=_masked?State.MASK:State.PAYLOAD;
+                        }
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case LENGTH_16:
+                        b=_buffer.get();
+                        available--;
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            if (_length>_buffer.capacity() && !_fragmentFrames)
+                            {
+                                progress=true;
+                                _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
+                                _skip=true;
+                            }
+
+                            _state=_masked?State.MASK:State.PAYLOAD;
+                            _bytesNeeded=_state.getNeeds();
+                        }
+                        continue;
+
+                    case LENGTH_63:
+                        b=_buffer.get();
+                        available--;
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            _bytesNeeded=(int)_length;
+                            if (_length>=_buffer.capacity() && !_fragmentFrames)
+                            {
+                                progress=true;
+                                _handler.close(WebSocketConnectionD08.CLOSE_BADDATA,"frame size "+_length+">"+_buffer.capacity());
+                                _skip=true;
+                            }
+
+                            _state=_masked?State.MASK:State.PAYLOAD;
+                            _bytesNeeded=_state.getNeeds();
+                        }
+                        continue;
+
+                    case MASK:
+                        _buffer.get(_mask,0,4);
+                        _m=0;
+                        available-=4;
+                        _state=State.PAYLOAD;
+                        _bytesNeeded=_state.getNeeds();
+                        break;
+
+                    case PAYLOAD:
+                        _bytesNeeded=(int)_length;
+                        _state=_skip?State.SKIP:State.DATA;
+                        break;
+
+                    case DATA:
+                        break;
+
+                    case SKIP:
+                        int skip=Math.min(available,_bytesNeeded);
+                        progress=true;
+                        _buffer.skip(skip);
+                        available-=skip;
+                        _bytesNeeded-=skip;
+                        if (_bytesNeeded==0)
+                            _state=State.START;
+                        break;
+
+                    case SEEK_EOF:
+                        progress=true;
+                        _buffer.skip(available);
+                        available=0;
+                        break;
+                }
+            }
+
+            if (_state==State.DATA && available>=_bytesNeeded)
+            {
+                if ( _masked!=_shouldBeMasked)
+                {
+                    _buffer.skip(_bytesNeeded);
+                    _state=State.START;
+                    progress=true;
+                    _handler.close(WebSocketConnectionD08.CLOSE_PROTOCOL,"bad mask");
+                }
+                else
+                {
+                    Buffer data =_buffer.get(_bytesNeeded);
+                    if (_masked)
+                    {
+                        if (data.array()==null)
+                            data=_buffer.asMutableBuffer();
+                        byte[] array = data.array();
+                        final int end=data.putIndex();
+                        for (int i=data.getIndex();i<end;i++)
+                            array[i]^=_mask[_m++%4];
+                    }
+
+                    // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+
+                    progress=true;
+                    _handler.onFrame(_flags, _opcode, data);
+                    _bytesNeeded=0;
+                    _state=State.START;
+                }
+
+                break;
+            }
+        }
+
+        return progress?1:filled;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fill(Buffer buffer)
+    {
+        if (buffer!=null && buffer.length()>0)
+        {
+            if (_buffer==null)
+                _buffer=_buffers.getBuffer();
+
+            _buffer.put(buffer);
+            buffer.clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void returnBuffer()
+    {
+        if (_buffer!=null && _buffer.length()==0)
+        {
+            _buffers.returnBuffer(_buffer);
+            _buffer=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x state=%s buffer=%s",
+                getClass().getSimpleName(),
+                hashCode(),
+                _state,
+                _buffer);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java b/src/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
new file mode 100644
index 0000000..9f0f8e1
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketParserRFC6455.java
@@ -0,0 +1,394 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+
+import org.eclipse.jetty.io.Buffer;
+import org.eclipse.jetty.io.Buffers;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+
+/* ------------------------------------------------------------ */
+/**
+ * Parser the WebSocket protocol.
+ *
+ */
+public class WebSocketParserRFC6455 implements WebSocketParser
+{
+    private static final Logger LOG = Log.getLogger(WebSocketParserRFC6455.class);
+
+    public enum State {
+
+        START(0), OPCODE(1), LENGTH_7(1), LENGTH_16(2), LENGTH_63(8), MASK(4), PAYLOAD(0), DATA(0), SKIP(1), SEEK_EOF(1);
+
+        int _needs;
+
+        State(int needs)
+        {
+            _needs=needs;
+        }
+
+        int getNeeds()
+        {
+            return _needs;
+        }
+    }
+
+    private final WebSocketBuffers _buffers;
+    private final EndPoint _endp;
+    private final FrameHandler _handler;
+    private final boolean _shouldBeMasked;
+    private State _state;
+    private Buffer _buffer;
+    private byte _flags;
+    private byte _opcode;
+    private int _bytesNeeded;
+    private long _length;
+    private boolean _masked;
+    private final byte[] _mask = new byte[4];
+    private int _m;
+    private boolean _skip;
+    private boolean _fragmentFrames=true;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param buffers The buffers to use for parsing.  Only the {@link Buffers#getBuffer()} is used.
+     * This should be a direct buffer if binary data is mostly used or an indirect buffer if utf-8 data
+     * is mostly used.
+     * @param endp the endpoint
+     * @param handler the handler to notify when a parse event occurs
+     * @param shouldBeMasked whether masking should be handled
+     */
+    public WebSocketParserRFC6455(WebSocketBuffers buffers, EndPoint endp, FrameHandler handler, boolean shouldBeMasked)
+    {
+        _buffers=buffers;
+        _endp=endp;
+        _handler=handler;
+        _shouldBeMasked=shouldBeMasked;
+        _state=State.START;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @return True if fake fragments should be created for frames larger than the buffer.
+     */
+    public boolean isFakeFragments()
+    {
+        return _fragmentFrames;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
+     */
+    public void setFakeFragments(boolean fakeFragments)
+    {
+        _fragmentFrames = fakeFragments;
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean isBufferEmpty()
+    {
+        return _buffer==null || _buffer.length()==0;
+    }
+
+    /* ------------------------------------------------------------ */
+    public Buffer getBuffer()
+    {
+        return _buffer;
+    }
+
+    /* ------------------------------------------------------------ */
+    /** Parse to next event.
+     * Parse to the next {@link WebSocketParser.FrameHandler} event or until no more data is
+     * available. Fill data from the {@link EndPoint} only as necessary.
+     * @return An indication of progress or otherwise. -1 indicates EOF, 0 indicates
+     * that no bytes were read and no messages parsed. A positive number indicates either
+     * the bytes filled or the messages parsed.
+     */
+    public int parseNext()
+    {
+        if (_buffer==null)
+            _buffer=_buffers.getBuffer();
+
+        boolean progress=false;
+        int filled=-1;
+
+        // Loop until a datagram call back or can't fill anymore
+        while(!progress && (!_endp.isInputShutdown()||_buffer.length()>0))
+        {
+            int available=_buffer.length();
+
+            // Fill buffer if we need a byte or need length
+            while (available<(_state==State.SKIP?1:_bytesNeeded))
+            {
+                // compact to mark (set at start of data)
+                _buffer.compact();
+
+                // if no space, then the data is too big for buffer
+                if (_buffer.space() == 0)
+                {
+                    // Can we send a fake frame?
+                    if (_fragmentFrames && _state==State.DATA)
+                    {
+                        Buffer data =_buffer.get(4*(available/4));
+                        _buffer.compact();
+                        if (_masked)
+                        {
+                            if (data.array()==null)
+                                data=_buffer.asMutableBuffer();
+                            byte[] array = data.array();
+                            final int end=data.putIndex();
+                            for (int i=data.getIndex();i<end;i++)
+                                array[i]^=_mask[_m++%4];
+                        }
+
+                        // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+                        _bytesNeeded-=data.length();
+                        progress=true;
+                        _handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionRFC6455.FLAG_FIN)), _opcode, data);
+
+                        _opcode=WebSocketConnectionRFC6455.OP_CONTINUATION;
+                    }
+
+                    if (_buffer.space() == 0)
+                        throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
+                }
+
+                // catch IOExceptions (probably EOF) and try to parse what we have
+                try
+                {
+                    filled=_endp.isInputShutdown()?-1:_endp.fill(_buffer);
+                    available=_buffer.length();
+                    // System.err.printf(">> filled %d/%d%n",filled,available);
+                    if (filled<=0)
+                        break;
+                }
+                catch(IOException e)
+                {
+                    LOG.debug(e);
+                    filled=-1;
+                    break;
+                }
+            }
+            // Did we get enough?
+            if (available<(_state==State.SKIP?1:_bytesNeeded))
+                break;
+
+            // if we are here, then we have sufficient bytes to process the current state.
+            // Parse the buffer byte by byte (unless it is STATE_DATA)
+            byte b;
+            while (_state!=State.DATA && available>=(_state==State.SKIP?1:_bytesNeeded))
+            {
+                switch (_state)
+                {
+                    case START:
+                        _skip=false;
+                        _state=_opcode==WebSocketConnectionRFC6455.OP_CLOSE?State.SEEK_EOF:State.OPCODE;
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case OPCODE:
+                        b=_buffer.get();
+                        available--;
+                        _opcode=(byte)(b&0xf);
+                        _flags=(byte)(0xf&(b>>4));
+
+                        if (WebSocketConnectionRFC6455.isControlFrame(_opcode)&&!WebSocketConnectionRFC6455.isLastFrame(_flags))
+                        {
+                            LOG.warn("Fragmented Control from "+_endp);
+                            _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Fragmented control");
+                            progress=true;
+                            _skip=true;
+                        }
+
+                        _state=State.LENGTH_7;
+                        _bytesNeeded=_state.getNeeds();
+
+                        continue;
+
+                    case LENGTH_7:
+                        b=_buffer.get();
+                        available--;
+                        _masked=(b&0x80)!=0;
+                        b=(byte)(0x7f&b);
+
+                        switch(b)
+                        {
+                            case 0x7f:
+                                _length=0;
+                                _state=State.LENGTH_63;
+                                break;
+                            case 0x7e:
+                                _length=0;
+                                _state=State.LENGTH_16;
+                                break;
+                            default:
+                                _length=(0x7f&b);
+                                _state=_masked?State.MASK:State.PAYLOAD;
+                        }
+                        _bytesNeeded=_state.getNeeds();
+                        continue;
+
+                    case LENGTH_16:
+                        b=_buffer.get();
+                        available--;
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            if (_length>_buffer.capacity() && !_fragmentFrames)
+                            {
+                                progress=true;
+                                _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
+                                _skip=true;
+                            }
+
+                            _state=_masked?State.MASK:State.PAYLOAD;
+                            _bytesNeeded=_state.getNeeds();
+                        }
+                        continue;
+
+                    case LENGTH_63:
+                        b=_buffer.get();
+                        available--;
+                        _length = _length*0x100 + (0xff&b);
+                        if (--_bytesNeeded==0)
+                        {
+                            _bytesNeeded=(int)_length;
+                            if (_length>=_buffer.capacity() && !_fragmentFrames)
+                            {
+                                progress=true;
+                                _handler.close(WebSocketConnectionRFC6455.CLOSE_POLICY_VIOLATION,"frame size "+_length+">"+_buffer.capacity());
+                                _skip=true;
+                            }
+
+                            _state=_masked?State.MASK:State.PAYLOAD;
+                            _bytesNeeded=_state.getNeeds();
+                        }
+                        continue;
+
+                    case MASK:
+                        _buffer.get(_mask,0,4);
+                        _m=0;
+                        available-=4;
+                        _state=State.PAYLOAD;
+                        _bytesNeeded=_state.getNeeds();
+                        break;
+
+                    case PAYLOAD:
+                        _bytesNeeded=(int)_length;
+                        _state=_skip?State.SKIP:State.DATA;
+                        break;
+
+                    case DATA:
+                        break;
+
+                    case SKIP:
+                        int skip=Math.min(available,_bytesNeeded);
+                        progress=true;
+                        _buffer.skip(skip);
+                        available-=skip;
+                        _bytesNeeded-=skip;
+                        if (_bytesNeeded==0)
+                            _state=State.START;
+                        break;
+
+                    case SEEK_EOF:
+                        progress=true;
+                        _buffer.skip(available);
+                        available=0;
+                        break;
+                }
+            }
+
+            if (_state==State.DATA && available>=_bytesNeeded)
+            {
+                if ( _masked!=_shouldBeMasked)
+                {
+                    _buffer.skip(_bytesNeeded);
+                    _state=State.START;
+                    progress=true;
+                    _handler.close(WebSocketConnectionRFC6455.CLOSE_PROTOCOL,"Not masked");
+                }
+                else
+                {
+                    Buffer data =_buffer.get(_bytesNeeded);
+                    if (_masked)
+                    {
+                        if (data.array()==null)
+                            data=_buffer.asMutableBuffer();
+                        byte[] array = data.array();
+                        final int end=data.putIndex();
+                        for (int i=data.getIndex();i<end;i++)
+                            array[i]^=_mask[_m++%4];
+                    }
+
+                    // System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
+
+                    progress=true;
+                    _handler.onFrame(_flags, _opcode, data);
+                    _bytesNeeded=0;
+                    _state=State.START;
+                }
+
+                break;
+            }
+        }
+
+        return progress?1:filled;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void fill(Buffer buffer)
+    {
+        if (buffer!=null && buffer.length()>0)
+        {
+            if (_buffer==null)
+                _buffer=_buffers.getBuffer();
+
+            _buffer.put(buffer);
+            buffer.clear();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    public void returnBuffer()
+    {
+        if (_buffer!=null && _buffer.length()==0)
+        {
+            _buffers.returnBuffer(_buffer);
+            _buffer=null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public String toString()
+    {
+        return String.format("%s@%x state=%s buffer=%s",
+                getClass().getSimpleName(),
+                hashCode(),
+                _state,
+                _buffer);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServlet.java b/src/java/org/eclipse/jetty/websocket/WebSocketServlet.java
new file mode 100644
index 0000000..71c2e2f
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServlet.java
@@ -0,0 +1,128 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/* ------------------------------------------------------------ */
+/**
+ * Servlet to upgrade connections to WebSocket
+ * <p/>
+ * The request must have the correct upgrade headers, else it is
+ * handled as a normal servlet request.
+ * <p/>
+ * The initParameter "bufferSize" can be used to set the buffer size,
+ * which is also the max frame byte size (default 8192).
+ * <p/>
+ * The initParameter "maxIdleTime" can be used to set the time in ms
+ * that a websocket may be idle before closing. (default is 300000)
+ * <p/>
+ * The initParameter "maxTextMessageSize" can be used to set the size in characters
+ * that a websocket may be accept before closing. (Default is 16768)
+ * <p/>
+ * The initParameter "maxBinaryMessageSize" can be used to set the size in bytes
+ * that a websocket may be accept before closing. (Default is -1 - or unlimited)
+ * <p/>
+ * The initParameter "minVersion" can be used to set the minimum protocol version
+ * accepted. (Default 13 - the RFC6455 version)
+ */
+@SuppressWarnings("serial")
+public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor
+{
+    private final Logger LOG = Log.getLogger(getClass());
+    private WebSocketFactory _webSocketFactory;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.GenericServlet#init()
+     */
+    @Override
+    public void init() throws ServletException
+    {
+        try
+        {
+            String bs = getInitParameter("bufferSize");
+            _webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs));
+            _webSocketFactory.start();
+
+            String max = getInitParameter("maxIdleTime");
+            if (max != null)
+                _webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
+
+            max = getInitParameter("maxTextMessageSize");
+            if (max != null)
+                _webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
+
+            max = getInitParameter("maxBinaryMessageSize");
+            if (max != null)
+                _webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
+            
+            String min = getInitParameter("minVersion");
+            if (min != null)
+                _webSocketFactory.setMinVersion(Integer.parseInt(min));
+        }
+        catch (ServletException x)
+        {
+            throw x;
+        }
+        catch (Exception x)
+        {
+            throw new ServletException(x);
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        if (_webSocketFactory.acceptWebSocket(request, response) || response.isCommitted())
+            return;
+        super.service(request, response);
+    }
+
+    /* ------------------------------------------------------------ */
+    public boolean checkOrigin(HttpServletRequest request, String origin)
+    {
+        return true;
+    }
+
+    /* ------------------------------------------------------------ */
+    @Override
+    public void destroy()
+    {
+        try
+        {
+            _webSocketFactory.stop();
+        }
+        catch (Exception x)
+        {
+            LOG.ignore(x);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java
new file mode 100644
index 0000000..56425b3
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnection.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public interface WebSocketServletConnection extends WebSocketConnection
+{
+    void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException;
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
new file mode 100644
index 0000000..9e98940
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
@@ -0,0 +1,105 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpURI;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.util.QuotedStringTokenizer;
+
+public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implements WebSocketServletConnection
+{
+    private final WebSocketFactory factory;
+
+    public WebSocketServletConnectionD00(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
+            throws IOException
+    {
+        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
+        this.factory = factory;
+    }
+
+    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
+    {
+        String uri = request.getRequestURI();
+        String query = request.getQueryString();
+        if (query != null && query.length() > 0)
+        {
+            uri += "?" + query;
+        }
+        uri = new HttpURI(uri).toString();
+        String host = request.getHeader("Host");
+
+        String origin = request.getHeader("Sec-WebSocket-Origin");
+        if (origin == null)
+        {
+            origin = request.getHeader("Origin");
+        }
+        if (origin != null)
+        {
+            origin = QuotedStringTokenizer.quoteIfNeeded(origin,"\r\n");
+        }
+
+        String key1 = request.getHeader("Sec-WebSocket-Key1");
+
+        if (key1 != null)
+        {
+            String key2 = request.getHeader("Sec-WebSocket-Key2");
+            setHixieKeys(key1,key2);
+
+            response.setHeader("Upgrade","WebSocket");
+            response.addHeader("Connection","Upgrade");
+            if (origin != null)
+            {
+                response.addHeader("Sec-WebSocket-Origin",origin);
+            }
+            response.addHeader("Sec-WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri);
+            if (subprotocol != null)
+            {
+                response.addHeader("Sec-WebSocket-Protocol",subprotocol);
+            }
+            response.sendError(101, "WebSocket Protocol Handshake");
+        }
+        else
+        {
+            response.setHeader("Upgrade","WebSocket");
+            response.addHeader("Connection","Upgrade");
+            response.addHeader("WebSocket-Origin",origin);
+            response.addHeader("WebSocket-Location",(request.isSecure()?"wss://":"ws://") + host + uri);
+            if (subprotocol != null)
+            {
+                response.addHeader("WebSocket-Protocol",subprotocol);
+            }
+            response.sendError(101,"Web Socket Protocol Handshake");
+            response.flushBuffer();
+
+            onFrameHandshake();
+            onWebsocketOpen();
+        }
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        factory.removeConnection(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
new file mode 100644
index 0000000..65578b9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
@@ -0,0 +1,63 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+
+public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implements WebSocketServletConnection
+{
+    private final WebSocketFactory factory;
+
+    public WebSocketServletConnectionD06(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
+            throws IOException
+    {
+        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
+        this.factory = factory;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
+    {
+        String key = request.getHeader("Sec-WebSocket-Key");
+
+        response.setHeader("Upgrade","WebSocket");
+        response.addHeader("Connection","Upgrade");
+        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
+        if (subprotocol!=null)
+        {
+            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
+        }
+
+        response.sendError(101);
+
+        onFrameHandshake();
+        onWebSocketOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        factory.removeConnection(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
new file mode 100644
index 0000000..8c0a14b
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+
+public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection
+{
+    private final WebSocketFactory factory;
+
+    public WebSocketServletConnectionD08(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
+            List<Extension> extensions, int draft) throws IOException
+    {
+        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
+        this.factory = factory;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
+    {
+        String key = request.getHeader("Sec-WebSocket-Key");
+
+        response.setHeader("Upgrade","WebSocket");
+        response.addHeader("Connection","Upgrade");
+        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
+        if (subprotocol != null)
+        {
+            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
+        }
+
+        for (Extension ext : getExtensions())
+        {
+            response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
+        }
+
+        response.sendError(101);
+
+        onFrameHandshake();
+        onWebSocketOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        factory.removeConnection(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
new file mode 100644
index 0000000..4ca1fe9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
@@ -0,0 +1,69 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+import java.io.IOException;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.io.EndPoint;
+
+public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection
+{
+    private final WebSocketFactory factory;
+
+    public WebSocketServletConnectionRFC6455(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
+            List<Extension> extensions, int draft) throws IOException
+    {
+        super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
+        this.factory = factory;
+    }
+
+    /* ------------------------------------------------------------ */
+    public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
+    {
+        String key = request.getHeader("Sec-WebSocket-Key");
+
+        response.setHeader("Upgrade","WebSocket");
+        response.addHeader("Connection","Upgrade");
+        response.addHeader("Sec-WebSocket-Accept",hashKey(key));
+        if (subprotocol != null)
+        {
+            response.addHeader("Sec-WebSocket-Protocol",subprotocol);
+        }
+
+        for (Extension ext : getExtensions())
+        {
+            response.addHeader("Sec-WebSocket-Extensions",ext.getParameterizedName());
+        }
+
+        response.sendError(101);
+
+        onFrameHandshake();
+        onWebSocketOpen();
+    }
+
+    @Override
+    public void onClose()
+    {
+        super.onClose();
+        factory.removeConnection(this);
+    }
+}
diff --git a/src/java/org/eclipse/jetty/websocket/ZeroMaskGen.java b/src/java/org/eclipse/jetty/websocket/ZeroMaskGen.java
new file mode 100644
index 0000000..dbcd9fd
--- /dev/null
+++ b/src/java/org/eclipse/jetty/websocket/ZeroMaskGen.java
@@ -0,0 +1,28 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.websocket;
+
+
+public class ZeroMaskGen implements MaskGen
+{
+    public void genMask(byte[] mask)
+    {
+        mask[0]=mask[1]=mask[2]=mask[3]=0;
+    }
+}
diff --git a/src/java/org/eclipse/jetty/xml/ConfigurationProcessor.java b/src/java/org/eclipse/jetty/xml/ConfigurationProcessor.java
new file mode 100644
index 0000000..8b7fe25
--- /dev/null
+++ b/src/java/org/eclipse/jetty/xml/ConfigurationProcessor.java
@@ -0,0 +1,38 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import java.net.URL;
+
+/**
+ * A ConfigurationProcessor for non XmlConfiguration format files.
+ * <p>
+ * A file in non-XmlConfiguration file format may be processed by a {@link ConfigurationProcessor}
+ * instance that is returned from a {@link ConfigurationProcessorFactory} instance discovered by the
+ * <code>ServiceLoader</code> mechanism.  This is used to allow spring configuration files to be used instead of 
+ * jetty.xml
+ *
+ */
+public interface ConfigurationProcessor
+{
+    public void init(URL url, XmlParser.Node root, XmlConfiguration configuration);
+    
+    public Object configure( Object obj) throws Exception;
+    public Object configure() throws Exception;
+}
diff --git a/src/java/org/eclipse/jetty/xml/ConfigurationProcessorFactory.java b/src/java/org/eclipse/jetty/xml/ConfigurationProcessorFactory.java
new file mode 100644
index 0000000..fc7476c
--- /dev/null
+++ b/src/java/org/eclipse/jetty/xml/ConfigurationProcessorFactory.java
@@ -0,0 +1,24 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+public interface ConfigurationProcessorFactory
+{
+    ConfigurationProcessor getConfigurationProcessor(String dtd, String tag);
+}
diff --git a/src/java/org/eclipse/jetty/xml/XmlConfiguration.java b/src/java/org/eclipse/jetty/xml/XmlConfiguration.java
new file mode 100644
index 0000000..0526ec9
--- /dev/null
+++ b/src/java/org/eclipse/jetty/xml/XmlConfiguration.java
@@ -0,0 +1,1299 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.ArrayQueue;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.Loader;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlParser.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/* ------------------------------------------------------------ */
+/**
+ * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and uses it to configure and object by calling set, put or other
+ * methods on the object.
+ *
+ * <p>
+ * The actual XML file format may be changed (eg to spring XML) by implementing the {@link ConfigurationProcessorFactory} interfaces to be found by the
+ * <code>ServiceLoader</code> by using the DTD and first tag element in the file. Note that DTD will be null if validation is off.
+ *
+ */
+public class XmlConfiguration
+{
+    private static final Logger LOG = Log.getLogger(XmlConfiguration.class);
+
+    private static final Class<?>[] __primitives =
+    { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE };
+
+    private static final Class<?>[] __primitiveHolders =
+    { Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class };
+
+    private static final Class<?>[] __supportedCollections =
+    { ArrayList.class,ArrayQueue.class,HashSet.class,Queue.class,List.class,Set.class,Collection.class,};
+
+    private static final Iterable<?> __factoryLoader;
+
+    private static final XmlParser __parser = initParser();
+
+    static
+    {
+        Iterable<?> loader=null;
+        try
+        {
+            // Use reflection to look up 1.6 service loader
+            // loader=ServiceLoader.load(ConfigurationProcessorFactory.class);
+            Class<?> slc = ClassLoader.getSystemClassLoader().loadClass("java.util.ServiceLoader");
+            Method load = slc.getMethod("load",Class.class);
+            loader=(Iterable<?>)load.invoke(null,ConfigurationProcessorFactory.class);
+        }
+        catch(Exception e)
+        {
+            LOG.ignore(e);
+        }
+        finally
+        {
+            __factoryLoader=loader;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private URL _url;
+    private String _dtd;
+    private ConfigurationProcessor _processor;
+    private final Map<String, Object> _idMap = new HashMap<String, Object>();
+    private final Map<String, String> _propertyMap = new HashMap<String, String>();
+
+    /* ------------------------------------------------------------ */
+    private synchronized static XmlParser initParser()
+    {
+        XmlParser parser = new XmlParser();
+        URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
+        URL config76 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_6.dtd",true);
+        parser.redirectEntity("configure.dtd",config76);
+        parser.redirectEntity("configure_1_0.dtd",config60);
+        parser.redirectEntity("configure_1_1.dtd",config60);
+        parser.redirectEntity("configure_1_2.dtd",config60);
+        parser.redirectEntity("configure_1_3.dtd",config60);
+        parser.redirectEntity("configure_6_0.dtd",config60);
+        parser.redirectEntity("configure_7_6.dtd",config76);
+
+        parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config76);
+        parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config76);
+        parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config76);
+
+        parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config76);
+        parser.redirectEntity("-//Jetty//Configure//EN",config76);
+
+        return parser;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reads and parses the XML configuration file.
+     *
+     * @param configuration the URL of the XML configuration
+     * @throws IOException if the configuration could not be read
+     * @throws SAXException if the configuration could not be parsed
+     */
+    public XmlConfiguration(URL configuration) throws SAXException, IOException
+    {
+        synchronized (__parser)
+        {
+            _url=configuration;
+            setConfig(__parser.parse(configuration.toString()));
+            _dtd=__parser.getDTD();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reads and parses the XML configuration string.
+     *
+     * @param configuration String of XML configuration commands excluding the normal XML preamble.
+     * The String should start with a "&lt;Configure ....&gt;" element.
+     * @throws IOException if the configuration could not be read
+     * @throws SAXException if the configuration could not be parsed
+     */
+    public XmlConfiguration(String configuration) throws SAXException, IOException
+    {
+        configuration = "<?xml version=\"1.0\"  encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.eclipse.org/configure_1_2.dtd\">"
+                + configuration;
+        InputSource source = new InputSource(new StringReader(configuration));
+        synchronized (__parser)
+        {
+            setConfig( __parser.parse(source));
+            _dtd=__parser.getDTD();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Reads and parses the XML configuration stream.
+     *
+     * @param configuration An input stream containing a complete configuration file
+     * @throws IOException if the configuration could not be read
+     * @throws SAXException if the configuration could not be parsed
+     */
+    public XmlConfiguration(InputStream configuration) throws SAXException, IOException
+    {
+        InputSource source = new InputSource(configuration);
+        synchronized (__parser)
+        {
+            setConfig(__parser.parse(source));
+            _dtd=__parser.getDTD();
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    private void setConfig(XmlParser.Node config)
+    {
+        if ("Configure".equals(config.getTag()))
+        {
+            _processor=new JettyXmlConfiguration();
+        }
+        else if (__factoryLoader!=null)
+        {
+            for ( Object factory : __factoryLoader)
+            {
+                // use reflection to get 1.6 methods
+                Method gcp;
+                try
+                {
+                    gcp = factory.getClass().getMethod("getConfigurationProcessor",String.class,String.class);
+                    _processor = (ConfigurationProcessor) gcp.invoke(factory,_dtd,config.getTag());
+                }
+                catch (Exception e)
+                {
+                    LOG.warn(e);
+                }
+                if (_processor!=null)
+                    break;
+            }
+
+            if (_processor==null)
+                throw new IllegalStateException("Unknown configuration type: "+config.getTag()+" in "+this);
+        }
+        else
+        {
+            throw new IllegalArgumentException("Unknown XML tag:"+config.getTag());
+        }
+        _processor.init(_url,config,this);
+    }
+
+
+    /* ------------------------------------------------------------ */
+    public Map<String, Object> getIdMap()
+    {
+        return _idMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param map the ID map
+     * @deprecated use {@link #getIdMap()}.put(...)
+     */
+    @Deprecated
+    public void setIdMap(Map<String, Object> map)
+    {
+        _idMap.clear();
+        _idMap.putAll(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * @param map the properties map
+     * @deprecated use {@link #getProperties()}.putAll(...)
+     */
+    @Deprecated
+    public void setProperties(Map<String, String> map)
+    {
+        _propertyMap.clear();
+        _propertyMap.putAll(map);
+    }
+
+    /* ------------------------------------------------------------ */
+    public Map<String, String> getProperties()
+    {
+        return _propertyMap;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Applies the XML configuration script to the given object.
+     *
+     * @param obj The object to be configured, which must be of a type or super type
+     * of the class attribute of the &lt;Configure&gt; element.
+     * @throws Exception if the configuration fails
+     * @return the configured object
+     */
+    public Object configure(Object obj) throws Exception
+    {
+        return _processor.configure(obj);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Applies the XML configuration script.
+     * If the root element of the configuration has an ID, an object is looked up by ID and its type checked
+     * against the root element's type.
+     * Otherwise a new object of the type specified by the root element is created.
+     *
+     * @return The newly created configured object.
+     * @throws Exception if the configuration fails
+     */
+    public Object configure() throws Exception
+    {
+        return _processor.configure();
+    }
+    
+    /* ------------------------------------------------------------ */
+    /** Initialize a new Object defaults.
+     * <p>This method must be called by any {@link ConfigurationProcessor} when it 
+     * creates a new instance of an object before configuring it, so that a derived 
+     * XmlConfiguration class may inject default values.
+     * @param object
+     */
+    public void initializeDefaults(Object object)
+    {
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private static class JettyXmlConfiguration implements ConfigurationProcessor
+    {
+        XmlParser.Node _root;
+        XmlConfiguration _configuration;
+
+        public void init(URL url, XmlParser.Node root, XmlConfiguration configuration)
+        {
+            _root=root;
+            _configuration=configuration;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Object configure(Object obj) throws Exception
+        {
+            // Check the class of the object
+            Class<?> oClass = nodeClass(_root);
+            if (oClass != null && !oClass.isInstance(obj))
+            {
+                String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders.";
+                throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders);
+            }
+            configure(obj,_root,0);
+            return obj;
+        }
+
+        /* ------------------------------------------------------------ */
+        public Object configure() throws Exception
+        {
+            Class<?> oClass = nodeClass(_root);
+
+            String id = _root.getAttribute("id");
+            Object obj = id == null?null:_configuration.getIdMap().get(id);
+
+            if (obj == null && oClass != null)
+            {
+                obj = oClass.newInstance();
+                _configuration.initializeDefaults(obj);
+            }
+
+            if (oClass != null && !oClass.isInstance(obj))
+                throw new ClassCastException(oClass.toString());
+
+            configure(obj,_root,0);
+            return obj;
+        }
+
+        /* ------------------------------------------------------------ */
+        private static Class<?> nodeClass(XmlParser.Node node) throws ClassNotFoundException
+        {
+            String className = node.getAttribute("class");
+            if (className == null)
+                return null;
+
+            return Loader.loadClass(XmlConfiguration.class,className,true);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Recursive configuration routine.
+         * This method applies the nested Set, Put, Call, etc. elements to the given object.
+         *
+         * @param obj the object to configure
+         * @param cfg the XML nodes of the configuration
+         * @param i the index of the XML nodes
+         * @throws Exception if the configuration fails
+         */
+        public void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
+        {
+            String id = cfg.getAttribute("id");
+            if (id != null)
+                _configuration.getIdMap().put(id,obj);
+
+            for (; i < cfg.size(); i++)
+            {
+                Object o = cfg.get(i);
+                if (o instanceof String)
+                    continue;
+                XmlParser.Node node = (XmlParser.Node)o;
+
+                try
+                {
+                    String tag = node.getTag();
+                    if ("Set".equals(tag))
+                        set(obj,node);
+                    else if ("Put".equals(tag))
+                        put(obj,node);
+                    else if ("Call".equals(tag))
+                        call(obj,node);
+                    else if ("Get".equals(tag))
+                        get(obj,node);
+                    else if ("New".equals(tag))
+                        newObj(obj,node);
+                    else if ("Array".equals(tag))
+                        newArray(obj,node);
+                    else if ("Ref".equals(tag))
+                        refObj(obj,node);
+                    else if ("Property".equals(tag))
+                        propertyObj(node);
+                    else
+                        throw new IllegalStateException("Unknown tag: " + tag);
+                }
+                catch (Exception e)
+                {
+                    LOG.warn("Config error at " + node,e.toString());
+                    throw e;
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Call a set method. This method makes a best effort to find a matching set method. The type of the value is used to find a suitable set method by 1.
+         * Trying for a trivial type match. 2. Looking for a native type match. 3. Trying all correctly named methods for an auto conversion. 4. Attempting to
+         * construct a suitable value from original value. @param obj
+         *
+         * @param node
+         */
+        private void set(Object obj, XmlParser.Node node) throws Exception
+        {
+            String attr = node.getAttribute("name");
+            String name = "set" + attr.substring(0,1).toUpperCase(Locale.ENGLISH) + attr.substring(1);
+            Object value = value(obj,node);
+            Object[] arg =
+            { value };
+
+            Class<?> oClass = nodeClass(node);
+            if (oClass != null)
+                obj = null;
+            else
+                oClass = obj.getClass();
+
+            Class<?>[] vClass =
+            { Object.class };
+            if (value != null)
+                vClass[0] = value.getClass();
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("XML " + (obj != null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");
+
+            // Try for trivial match
+            try
+            {
+                Method set = oClass.getMethod(name,vClass);
+                set.invoke(obj,arg);
+                return;
+            }
+            catch (IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (NoSuchMethodException e)
+            {
+                LOG.ignore(e);
+            }
+
+            // Try for native match
+            try
+            {
+                Field type = vClass[0].getField("TYPE");
+                vClass[0] = (Class<?>)type.get(null);
+                Method set = oClass.getMethod(name,vClass);
+                set.invoke(obj,arg);
+                return;
+            }
+            catch (NoSuchFieldException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (IllegalArgumentException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (IllegalAccessException e)
+            {
+                LOG.ignore(e);
+            }
+            catch (NoSuchMethodException e)
+            {
+                LOG.ignore(e);
+            }
+
+            // Try a field
+            try
+            {
+                Field field = oClass.getField(attr);
+                if (Modifier.isPublic(field.getModifiers()))
+                {
+                    field.set(obj,value);
+                    return;
+                }
+            }
+            catch (NoSuchFieldException e)
+            {
+                LOG.ignore(e);
+            }
+
+            // Search for a match by trying all the set methods
+            Method[] sets = oClass.getMethods();
+            Method set = null;
+            for (int s = 0; sets != null && s < sets.length; s++)
+            {
+                Class<?>[] paramTypes = sets[s].getParameterTypes();
+                if (name.equals(sets[s].getName()) && paramTypes.length == 1)
+                {
+
+                    // lets try it
+                    try
+                    {
+                        set = sets[s];
+                        sets[s].invoke(obj,arg);
+                        return;
+                    }
+                    catch (IllegalArgumentException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (IllegalAccessException e)
+                    {
+                        LOG.ignore(e);
+                    }
+
+                    try
+                    {
+                        for (Class<?> c : __supportedCollections)
+                            if (paramTypes[0].isAssignableFrom(c))
+                            {
+                                sets[s].invoke(obj,convertArrayToCollection(value,c));
+                                return;
+                            }
+                    }
+                    catch (IllegalAccessException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                }
+            }
+
+            // Try converting the arg to the last set found.
+            if (set != null)
+            {
+                try
+                {
+                    Class<?> sClass = set.getParameterTypes()[0];
+                    if (sClass.isPrimitive())
+                    {
+                        for (int t = 0; t < __primitives.length; t++)
+                        {
+                            if (sClass.equals(__primitives[t]))
+                            {
+                                sClass = __primitiveHolders[t];
+                                break;
+                            }
+                        }
+                    }
+                    Constructor<?> cons = sClass.getConstructor(vClass);
+                    arg[0] = cons.newInstance(arg);
+                    _configuration.initializeDefaults(arg[0]);
+                    set.invoke(obj,arg);
+                    return;
+                }
+                catch (NoSuchMethodException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (IllegalAccessException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (InstantiationException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+
+            // No Joy
+            throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
+        }
+
+        /**
+         * @param array the array to convert
+         * @param collectionType the desired collection type
+         * @return a collection of the desired type if the array can be converted
+         */
+        private static Collection<?> convertArrayToCollection(Object array, Class<?> collectionType)
+        {
+            Collection<?> collection = null;
+            if (array.getClass().isArray())
+            {
+                if (collectionType.isAssignableFrom(ArrayList.class))
+                    collection = convertArrayToArrayList(array);
+                else if (collectionType.isAssignableFrom(HashSet.class))
+                    collection = new HashSet<Object>(convertArrayToArrayList(array));
+                else if (collectionType.isAssignableFrom(ArrayQueue.class))
+                {
+                    ArrayQueue<Object> q= new ArrayQueue<Object>();
+                    q.addAll(convertArrayToArrayList(array));
+                    collection=q;
+                }
+            }
+            if (collection==null)
+                throw new IllegalArgumentException("Can't convert \"" + array.getClass() + "\" to " + collectionType);
+            return collection;
+        }
+
+        /* ------------------------------------------------------------ */
+        private static ArrayList<Object> convertArrayToArrayList(Object array)
+        {
+            int length = Array.getLength(array);
+            ArrayList<Object> list = new ArrayList<Object>(length);
+            for (int i = 0; i < length; i++)
+                list.add(Array.get(array,i));
+            return list;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Call a put method.
+         *
+         * @param obj @param node
+         */
+        private void put(Object obj, XmlParser.Node node) throws Exception
+        {
+            if (!(obj instanceof Map))
+                throw new IllegalArgumentException("Object for put is not a Map: " + obj);
+            @SuppressWarnings("unchecked")
+            Map<Object, Object> map = (Map<Object, Object>)obj;
+
+            String name = node.getAttribute("name");
+            Object value = value(obj,node);
+            map.put(name,value);
+            if (LOG.isDebugEnabled())
+                LOG.debug("XML " + obj + ".put(" + name + "," + value + ")");
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Call a get method. Any object returned from the call is passed to the configure method to consume the remaining elements. @param obj @param node
+         *
+         * @return @exception Exception
+         */
+        private Object get(Object obj, XmlParser.Node node) throws Exception
+        {
+            Class<?> oClass = nodeClass(node);
+            if (oClass != null)
+                obj = null;
+            else
+                oClass = obj.getClass();
+
+            String name = node.getAttribute("name");
+            String id = node.getAttribute("id");
+            if (LOG.isDebugEnabled())
+                LOG.debug("XML get " + name);
+
+            try
+            {
+                // try calling a getXxx method.
+                Method method = oClass.getMethod("get" + name.substring(0,1).toUpperCase(Locale.ENGLISH) + name.substring(1),(java.lang.Class[])null);
+                obj = method.invoke(obj,(java.lang.Object[])null);
+                configure(obj,node,0);
+            }
+            catch (NoSuchMethodException nsme)
+            {
+                try
+                {
+                    Field field = oClass.getField(name);
+                    obj = field.get(obj);
+                    configure(obj,node,0);
+                }
+                catch (NoSuchFieldException nsfe)
+                {
+                    throw nsme;
+                }
+            }
+            if (id != null)
+                _configuration.getIdMap().put(id,obj);
+            return obj;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Call a method. A method is selected by trying all methods with matching names and number of arguments. Any object returned from the call is passed to
+         * the configure method to consume the remaining elements. Note that if this is a static call we consider only methods declared directly in the given
+         * class. i.e. we ignore any static methods in superclasses. @param obj
+         *
+         * @param node @return @exception Exception
+         */
+        private Object call(Object obj, XmlParser.Node node) throws Exception
+        {
+            String id = node.getAttribute("id");
+            Class<?> oClass = nodeClass(node);
+            if (oClass != null)
+                obj = null;
+            else if (obj != null)
+                oClass = obj.getClass();
+            if (oClass == null)
+                throw new IllegalArgumentException(node.toString());
+
+            int size = 0;
+            int argi = node.size();
+            for (int i = 0; i < node.size(); i++)
+            {
+                Object o = node.get(i);
+                if (o instanceof String)
+                    continue;
+                if (!((XmlParser.Node)o).getTag().equals("Arg"))
+                {
+                    argi = i;
+                    break;
+                }
+                size++;
+            }
+
+            Object[] arg = new Object[size];
+            for (int i = 0, j = 0; j < size; i++)
+            {
+                Object o = node.get(i);
+                if (o instanceof String)
+                    continue;
+                arg[j++] = value(obj,(XmlParser.Node)o);
+            }
+
+            String method = node.getAttribute("name");
+            if (LOG.isDebugEnabled())
+                LOG.debug("XML call " + method);
+
+            try
+            {
+                Object n= TypeUtil.call(oClass,method,obj,arg);
+                if (id != null)
+                    _configuration.getIdMap().put(id,n);
+                configure(n,node,argi);
+                return n;
+            }
+            catch (NoSuchMethodException e)
+            {
+                IllegalStateException ise = new IllegalStateException("No Method: " + node + " on " + oClass);
+                ise.initCause(e);
+                throw ise;
+            }
+
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Create a new value object.
+         *
+         * @param obj @param node @return @exception Exception
+         */
+        private Object newObj(Object obj, XmlParser.Node node) throws Exception
+        {
+            Class<?> oClass = nodeClass(node);
+            String id = node.getAttribute("id");
+            int size = 0;
+            int argi = node.size();
+            for (int i = 0; i < node.size(); i++)
+            {
+                Object o = node.get(i);
+                if (o instanceof String)
+                    continue;
+                if (!((XmlParser.Node)o).getTag().equals("Arg"))
+                {
+                    argi = i;
+                    break;
+                }
+                size++;
+            }
+
+            Object[] arg = new Object[size];
+            for (int i = 0, j = 0; j < size; i++)
+            {
+                Object o = node.get(i);
+                if (o instanceof String)
+                    continue;
+                arg[j++] = value(obj,(XmlParser.Node)o);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("XML new " + oClass);
+
+            // Lets just try all constructors for now
+            Constructor<?>[] constructors = oClass.getConstructors();
+            for (int c = 0; constructors != null && c < constructors.length; c++)
+            {
+                if (constructors[c].getParameterTypes().length != size)
+                    continue;
+
+                Object n = null;
+                boolean called = false;
+                try
+                {
+                    n = constructors[c].newInstance(arg);
+                    _configuration.initializeDefaults(n);
+                    called = true;
+                }
+                catch (IllegalAccessException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (InstantiationException e)
+                {
+                    LOG.ignore(e);
+                }
+                catch (IllegalArgumentException e)
+                {
+                    LOG.ignore(e);
+                }
+                if (called)
+                {
+                    if (id != null)
+                        _configuration.getIdMap().put(id,n);
+                    configure(n,node,argi);
+                    return n;
+                }
+            }
+
+            throw new IllegalStateException("No Constructor: " + node + " on " + obj);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Reference an id value object.
+         *
+         * @param obj @param node @return @exception NoSuchMethodException @exception ClassNotFoundException @exception InvocationTargetException
+         */
+        private Object refObj(Object obj, XmlParser.Node node) throws Exception
+        {
+            String id = node.getAttribute("id");
+            obj = _configuration.getIdMap().get(id);
+            if (obj == null)
+                throw new IllegalStateException("No object for id=" + id);
+            configure(obj,node,0);
+            return obj;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Create a new array object.
+         */
+        private Object newArray(Object obj, XmlParser.Node node) throws Exception
+        {
+
+            // Get the type
+            Class<?> aClass = java.lang.Object.class;
+            String type = node.getAttribute("type");
+            final String id = node.getAttribute("id");
+            if (type != null)
+            {
+                aClass = TypeUtil.fromName(type);
+                if (aClass == null)
+                {
+                    if ("String".equals(type))
+                        aClass = java.lang.String.class;
+                    else if ("URL".equals(type))
+                        aClass = java.net.URL.class;
+                    else if ("InetAddress".equals(type))
+                        aClass = java.net.InetAddress.class;
+                    else
+                        aClass = Loader.loadClass(XmlConfiguration.class,type,true);
+                }
+            }
+
+            Object al = null;
+
+            for (Object nodeObject : node)
+            {
+                XmlParser.Node item = (Node)nodeObject;
+                String nid = item.getAttribute("id");
+                Object v = value(obj,item);
+                al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v);
+                if (nid != null)
+                    _configuration.getIdMap().put(nid,v);
+            }
+
+            Object array = LazyList.toArray(al,aClass);
+            if (id != null)
+                _configuration.getIdMap().put(id,array);
+            return array;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Create a new map object.
+         */
+        private Object newMap(Object obj, XmlParser.Node node) throws Exception
+        {
+            String id = node.getAttribute("id");
+
+            Map<Object, Object> map = new HashMap<Object, Object>();
+            if (id != null)
+                _configuration.getIdMap().put(id,map);
+
+            for (Object o : node)
+            {
+                if (o instanceof String)
+                    continue;
+                XmlParser.Node entry = (XmlParser.Node)o;
+                if (!entry.getTag().equals("Entry"))
+                    throw new IllegalStateException("Not an Entry");
+
+                XmlParser.Node key = null;
+                XmlParser.Node value = null;
+
+                for (Object object : entry)
+                {
+                    if (object instanceof String)
+                        continue;
+                    XmlParser.Node item = (XmlParser.Node)object;
+                    if (!item.getTag().equals("Item"))
+                        throw new IllegalStateException("Not an Item");
+                    if (key == null)
+                        key = item;
+                    else
+                        value = item;
+                }
+
+                if (key == null || value == null)
+                    throw new IllegalStateException("Missing Item in Entry");
+                String kid = key.getAttribute("id");
+                String vid = value.getAttribute("id");
+
+                Object k = value(obj,key);
+                Object v = value(obj,value);
+                map.put(k,v);
+
+                if (kid != null)
+                    _configuration.getIdMap().put(kid,k);
+                if (vid != null)
+                    _configuration.getIdMap().put(vid,v);
+            }
+
+            return map;
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Get a Property.
+         *
+         * @param node
+         * @return
+         * @exception Exception
+         */
+        private Object propertyObj(XmlParser.Node node) throws Exception
+        {
+            String id = node.getAttribute("id");
+            String name = node.getAttribute("name");
+            String defaultValue = node.getAttribute("default");
+            Object prop;
+            Map<String,String> property_map=_configuration.getProperties();
+            if (property_map != null && property_map.containsKey(name))
+                prop = property_map.get(name);
+            else
+                prop = defaultValue;
+            if (id != null)
+                _configuration.getIdMap().put(id,prop);
+            if (prop != null)
+                configure(prop,node,0);
+            return prop;
+        }
+
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
+         * are added as strings before being converted to any specified type. @param node
+         */
+        private Object value(Object obj, XmlParser.Node node) throws Exception
+        {
+            Object value;
+
+            // Get the type
+            String type = node.getAttribute("type");
+
+            // Try a ref lookup
+            String ref = node.getAttribute("ref");
+            if (ref != null)
+            {
+                value = _configuration.getIdMap().get(ref);
+            }
+            else
+            {
+                // handle trivial case
+                if (node.size() == 0)
+                {
+                    if ("String".equals(type))
+                        return "";
+                    return null;
+                }
+
+                // Trim values
+                int first = 0;
+                int last = node.size() - 1;
+
+                // Handle default trim type
+                if (type == null || !"String".equals(type))
+                {
+                    // Skip leading white
+                    Object item;
+                    while (first <= last)
+                    {
+                        item = node.get(first);
+                        if (!(item instanceof String))
+                            break;
+                        item = ((String)item).trim();
+                        if (((String)item).length() > 0)
+                            break;
+                        first++;
+                    }
+
+                    // Skip trailing white
+                    while (first < last)
+                    {
+                        item = node.get(last);
+                        if (!(item instanceof String))
+                            break;
+                        item = ((String)item).trim();
+                        if (((String)item).length() > 0)
+                            break;
+                        last--;
+                    }
+
+                    // All white, so return null
+                    if (first > last)
+                        return null;
+                }
+
+                if (first == last)
+                    // Single Item value
+                    value = itemValue(obj,node.get(first));
+                else
+                {
+                    // Get the multiple items as a single string
+                    StringBuilder buf = new StringBuilder();
+                    for (int i = first; i <= last; i++)
+                    {
+                        Object item = node.get(i);
+                        buf.append(itemValue(obj,item));
+                    }
+                    value = buf.toString();
+                }
+            }
+
+            // Untyped or unknown
+            if (value == null)
+            {
+                if ("String".equals(type))
+                    return "";
+                return null;
+            }
+
+            // Try to type the object
+            if (type == null)
+            {
+                if (value instanceof String)
+                    return ((String)value).trim();
+                return value;
+            }
+
+            if (isTypeMatchingClass(type,String.class))
+                return value.toString();
+
+            Class<?> pClass = TypeUtil.fromName(type);
+            if (pClass != null)
+                return TypeUtil.valueOf(pClass,value.toString());
+
+            if (isTypeMatchingClass(type,URL.class))
+            {
+                if (value instanceof URL)
+                    return value;
+                try
+                {
+                    return new URL(value.toString());
+                }
+                catch (MalformedURLException e)
+                {
+                    throw new InvocationTargetException(e);
+                }
+            }
+
+            if (isTypeMatchingClass(type,InetAddress.class))
+            {
+                if (value instanceof InetAddress)
+                    return value;
+                try
+                {
+                    return InetAddress.getByName(value.toString());
+                }
+                catch (UnknownHostException e)
+                {
+                    throw new InvocationTargetException(e);
+                }
+            }
+
+            for (Class<?> collectionClass : __supportedCollections)
+            {
+                if (isTypeMatchingClass(type,collectionClass))
+                    return convertArrayToCollection(value,collectionClass);
+            }
+
+            throw new IllegalStateException("Unknown type " + type);
+        }
+
+        /* ------------------------------------------------------------ */
+        private static boolean isTypeMatchingClass(String type, Class<?> classToMatch)
+        {
+            return classToMatch.getSimpleName().equalsIgnoreCase(type) || classToMatch.getName().equals(type);
+        }
+
+        /* ------------------------------------------------------------ */
+        /*
+         * Get the value of a single element. @param obj @param item @return @exception Exception
+         */
+        private Object itemValue(Object obj, Object item) throws Exception
+        {
+            // String value
+            if (item instanceof String)
+                return item;
+
+            XmlParser.Node node = (XmlParser.Node)item;
+            String tag = node.getTag();
+            if ("Call".equals(tag))
+                return call(obj,node);
+            if ("Get".equals(tag))
+                return get(obj,node);
+            if ("New".equals(tag))
+                return newObj(obj,node);
+            if ("Ref".equals(tag))
+                return refObj(obj,node);
+            if ("Array".equals(tag))
+                return newArray(obj,node);
+            if ("Map".equals(tag))
+                return newMap(obj,node);
+            if ("Property".equals(tag))
+                return propertyObj(node);
+
+            if ("SystemProperty".equals(tag))
+            {
+                String name = node.getAttribute("name");
+                String defaultValue = node.getAttribute("default");
+                return System.getProperty(name,defaultValue);
+            }
+
+            if ("Env".equals(tag))
+            {
+                String name = node.getAttribute("name");
+                String defaultValue = node.getAttribute("default");
+                String value=System.getenv(name);
+                return value==null?defaultValue:value;
+            }
+
+            LOG.warn("Unknown value tag: " + node,new Throwable());
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * Run the XML configurations as a main application. The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
+     * files.
+     * <p>
+     * Any property file on the command line is added to a combined Property instance that is passed to each configuration file via
+     * {@link XmlConfiguration#setProperties(Map)}.
+     * <p>
+     * Each configuration file on the command line is used to create a new XmlConfiguration instance and the {@link XmlConfiguration#configure()} method is used
+     * to create the configured object. If the resulting object is an instance of {@link LifeCycle}, then it is started.
+     * <p>
+     * Any IDs created in a configuration are passed to the next configuration file on the command line using {@link #getIdMap()} and {@link #setIdMap(Map)} .
+     * This allows objects with IDs created in one config file to be referenced in subsequent config files on the command line.
+     *
+     * @param args
+     *            array of property and xml configuration filenames or {@link Resource}s.
+     * @throws Exception if the XML configurations cannot be run
+     */
+    public static void main(final String[] args) throws Exception
+    {
+
+        final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+        AccessController.doPrivileged(new PrivilegedAction<Object>()
+        {
+            public Object run()
+            {
+                try
+                {
+
+                    Properties properties = null;
+
+                    // Look for properties from start.jar
+                    try
+                    {
+                        Class<?> config = XmlConfiguration.class.getClassLoader().loadClass("org.eclipse.jetty.start.Config");
+                        properties = (Properties)config.getMethod("getProperties").invoke(null);
+                        LOG.debug("org.eclipse.jetty.start.Config properties = {}",properties);
+                    }
+                    catch (NoClassDefFoundError e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (ClassNotFoundException e)
+                    {
+                        LOG.ignore(e);
+                    }
+                    catch (Exception e)
+                    {
+                        LOG.warn(e);
+                    }
+
+                    // If no start.config properties, use clean slate
+                    if (properties == null)
+                    {
+                        properties = new Properties();
+                        // Add System Properties
+                        Enumeration<?> ensysprop = System.getProperties().propertyNames();
+                        while (ensysprop.hasMoreElements())
+                        {
+                            String name = (String)ensysprop.nextElement();
+                            properties.put(name,System.getProperty(name));
+                        }
+                    }
+
+                    // For all arguments, load properties or parse XMLs
+                    XmlConfiguration last = null;
+                    Object[] obj = new Object[args.length];
+                    for (int i = 0; i < args.length; i++)
+                    {
+                        if (args[i].toLowerCase(Locale.ENGLISH).endsWith(".properties"))
+                        {
+                            properties.load(Resource.newResource(args[i]).getInputStream());
+                        }
+                        else
+                        {
+                            XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
+                            if (last != null)
+                                configuration.getIdMap().putAll(last.getIdMap());
+                            if (properties.size() > 0)
+                            {
+                                Map<String, String> props = new HashMap<String, String>();
+                                for (Object key : properties.keySet())
+                                {
+                                    props.put(key.toString(),String.valueOf(properties.get(key)));
+                                }
+                                configuration.getProperties().putAll(props);
+                            }
+                            obj[i] = configuration.configure();
+                            last = configuration;
+                        }
+                    }
+
+                    // For all objects created by XmlConfigurations, start them if they are lifecycles.
+                    for (int i = 0; i < args.length; i++)
+                    {
+                        if (obj[i] instanceof LifeCycle)
+                        {
+                            LifeCycle lc = (LifeCycle)obj[i];
+                            if (!lc.isRunning())
+                                lc.start();
+                        }
+                    }
+                }
+                catch (Exception e)
+                {
+                    LOG.debug(Log.EXCEPTION,e);
+                    exception.set(e);
+                }
+                return null;
+            }
+        });
+
+        Throwable th = exception.get();
+        if (th != null)
+        {
+            if (th instanceof RuntimeException)
+                throw (RuntimeException)th;
+            else if (th instanceof Exception)
+                throw (Exception)th;
+            else if (th instanceof Error)
+                throw (Error)th;
+            throw new Error(th);
+        }
+    }
+}
diff --git a/src/java/org/eclipse/jetty/xml/XmlParser.java b/src/java/org/eclipse/jetty/xml/XmlParser.java
new file mode 100644
index 0000000..6bf6628
--- /dev/null
+++ b/src/java/org/eclipse/jetty/xml/XmlParser.java
@@ -0,0 +1,820 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.xml;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/*--------------------------------------------------------------*/
+/**
+ * XML Parser wrapper. This class wraps any standard JAXP1.1 parser with convieniant error and
+ * entity handlers and a mini dom-like document tree.
+ * <P>
+ * By default, the parser is created as a validating parser only if xerces is present. This can be 
+ * configured by setting the "org.eclipse.jetty.xml.XmlParser.Validating" system property.
+ * 
+ * 
+ */
+public class XmlParser
+{
+    private static final Logger LOG = Log.getLogger(XmlParser.class);
+
+    private Map<String,URL> _redirectMap = new HashMap<String,URL>();
+    private SAXParser _parser;
+    private Map<String,ContentHandler> _observerMap;
+    private Stack<ContentHandler> _observers = new Stack<ContentHandler>();
+    private String _xpath;
+    private Object _xpaths;
+    private String _dtd;
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Construct
+     */
+    public XmlParser()
+    {
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        boolean validating_dft = factory.getClass().toString().startsWith("org.apache.xerces.");
+        String validating_prop = System.getProperty("org.eclipse.jetty.xml.XmlParser.Validating", validating_dft ? "true" : "false");
+        boolean validating = Boolean.valueOf(validating_prop).booleanValue();
+        setValidating(validating);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Constructor.
+     */
+    public XmlParser(boolean validating)
+    {
+        setValidating(validating);
+    }
+    
+    /* ------------------------------------------------------------ */
+    public void setValidating(boolean validating)
+    {
+        try
+        {
+            SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setValidating(validating);
+            _parser = factory.newSAXParser();
+            
+            try
+            {
+                if (validating)
+                    _parser.getXMLReader().setFeature("http://apache.org/xml/features/validation/schema", validating);
+            }
+            catch (Exception e)
+            {
+                if (validating)
+                    LOG.warn("Schema validation may not be supported: ", e);
+                else
+                    LOG.ignore(e);
+            }
+
+            _parser.getXMLReader().setFeature("http://xml.org/sax/features/validation", validating);
+            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespaces", true);
+            _parser.getXMLReader().setFeature("http://xml.org/sax/features/namespace-prefixes", false);  
+            try
+            {
+                if (validating)
+                    _parser.getXMLReader().setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", validating);
+            }
+            catch (Exception e)
+            {
+                LOG.warn(e.getMessage());
+            }
+        }
+        catch (Exception e)
+        {
+            LOG.warn(Log.EXCEPTION, e);
+            throw new Error(e.toString());
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /**
+     * @param name
+     * @param entity
+     */
+    public synchronized void redirectEntity(String name, URL entity)
+    {
+        if (entity != null)
+            _redirectMap.put(name, entity);
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * 
+     * @return Returns the xpath.
+     */
+    public String getXpath()
+    {
+        return _xpath;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Set an XPath A very simple subset of xpath is supported to select a partial tree. Currently
+     * only path like "/node1/nodeA | /node1/nodeB" are supported.
+     * 
+     * @param xpath The xpath to set.
+     */
+    public void setXpath(String xpath)
+    {
+        _xpath = xpath;
+        StringTokenizer tok = new StringTokenizer(xpath, "| ");
+        while (tok.hasMoreTokens())
+            _xpaths = LazyList.add(_xpaths, tok.nextToken());
+    }
+
+    /* ------------------------------------------------------------ */
+    public String getDTD()
+    {
+        return _dtd;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Add a ContentHandler. Add an additional _content handler that is triggered on a tag name. SAX
+     * events are passed to the ContentHandler provided from a matching start element to the
+     * corresponding end element. Only a single _content handler can be registered against each tag.
+     * 
+     * @param trigger Tag local or q name.
+     * @param observer SAX ContentHandler
+     */
+    public synchronized void addContentHandler(String trigger, ContentHandler observer)
+    {
+        if (_observerMap == null)
+            _observerMap = new HashMap();
+        _observerMap.put(trigger, observer);
+    }
+
+    /* ------------------------------------------------------------ */
+    public synchronized Node parse(InputSource source) throws IOException, SAXException
+    {
+        _dtd=null;
+        Handler handler = new Handler();
+        XMLReader reader = _parser.getXMLReader();
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+        reader.setEntityResolver(handler);
+        if (LOG.isDebugEnabled())
+            LOG.debug("parsing: sid=" + source.getSystemId() + ",pid=" + source.getPublicId());
+        _parser.parse(source, handler);
+        if (handler._error != null)
+            throw handler._error;
+        Node doc = (Node) handler._top.get(0);
+        handler.clear();
+        return doc;
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Parse String URL.
+     */
+    public synchronized Node parse(String url) throws IOException, SAXException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("parse: " + url);
+        return parse(new InputSource(url));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Parse File.
+     */
+    public synchronized Node parse(File file) throws IOException, SAXException
+    {
+        if (LOG.isDebugEnabled())
+            LOG.debug("parse: " + file);
+        return parse(new InputSource(Resource.toURL(file).toString()));
+    }
+
+    /* ------------------------------------------------------------ */
+    /**
+     * Parse InputStream.
+     */
+    public synchronized Node parse(InputStream in) throws IOException, SAXException
+    {
+        _dtd=null;
+        Handler handler = new Handler();
+        XMLReader reader = _parser.getXMLReader();
+        reader.setContentHandler(handler);
+        reader.setErrorHandler(handler);
+        reader.setEntityResolver(handler);
+        _parser.parse(new InputSource(in), handler);
+        if (handler._error != null)
+            throw handler._error;
+        Node doc = (Node) handler._top.get(0);
+        handler.clear();
+        return doc;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class NoopHandler extends DefaultHandler
+    {
+        Handler _next;
+        int _depth;
+
+        NoopHandler(Handler next)
+        {
+            this._next = next;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
+        {
+            _depth++;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void endElement(String uri, String localName, String qName) throws SAXException
+        {
+            if (_depth == 0)
+                _parser.getXMLReader().setContentHandler(_next);
+            else
+                _depth--;
+        }
+    }
+    
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    private class Handler extends DefaultHandler
+    {
+        Node _top = new Node(null, null, null);
+        SAXParseException _error;
+        private Node _context = _top;
+        private NoopHandler _noop;
+
+        Handler()
+        {
+            _noop = new NoopHandler(this);
+        }
+
+        /* ------------------------------------------------------------ */
+        void clear()
+        {
+            _top = null;
+            _error = null;
+            _context = null;
+        }
+
+        /* ------------------------------------------------------------ */
+        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException
+        {
+            String name = null;
+            if (_parser.isNamespaceAware())
+                name = localName;
+
+            if (name == null || "".equals(name))
+                name = qName;
+
+            Node node = new Node(_context, name, attrs);
+            
+
+            // check if the node matches any xpaths set?
+            if (_xpaths != null)
+            {
+                String path = node.getPath();
+                boolean match = false;
+                for (int i = LazyList.size(_xpaths); !match && i-- > 0;)
+                {
+                    String xpath = (String) LazyList.get(_xpaths, i);
+
+                    match = path.equals(xpath) || xpath.startsWith(path) && xpath.length() > path.length() && xpath.charAt(path.length()) == '/';
+                }
+
+                if (match)
+                {
+                    _context.add(node);
+                    _context = node;
+                }
+                else
+                {
+                    _parser.getXMLReader().setContentHandler(_noop);
+                }
+            }
+            else
+            {
+                _context.add(node);
+                _context = node;
+            }
+
+            ContentHandler observer = null;
+            if (_observerMap != null)
+                observer = (ContentHandler) _observerMap.get(name);
+            _observers.push(observer);
+
+            for (int i = 0; i < _observers.size(); i++)
+                if (_observers.get(i) != null)
+                    ((ContentHandler) _observers.get(i)).startElement(uri, localName, qName, attrs);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void endElement(String uri, String localName, String qName) throws SAXException
+        {
+            _context = _context._parent;
+            for (int i = 0; i < _observers.size(); i++)
+                if (_observers.get(i) != null)
+                    ((ContentHandler) _observers.get(i)).endElement(uri, localName, qName);
+            _observers.pop();
+        }
+
+        /* ------------------------------------------------------------ */
+        public void ignorableWhitespace(char buf[], int offset, int len) throws SAXException
+        {
+            for (int i = 0; i < _observers.size(); i++)
+                if (_observers.get(i) != null)
+                    ((ContentHandler) _observers.get(i)).ignorableWhitespace(buf, offset, len);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void characters(char buf[], int offset, int len) throws SAXException
+        {
+            _context.add(new String(buf, offset, len));
+            for (int i = 0; i < _observers.size(); i++)
+                if (_observers.get(i) != null)
+                    ((ContentHandler) _observers.get(i)).characters(buf, offset, len);
+        }
+
+        /* ------------------------------------------------------------ */
+        public void warning(SAXParseException ex)
+        {
+            LOG.debug(Log.EXCEPTION, ex);
+            LOG.warn("WARNING@" + getLocationString(ex) + " : " + ex.toString());
+        }
+
+        /* ------------------------------------------------------------ */
+        public void error(SAXParseException ex) throws SAXException
+        {
+            // Save error and continue to report other errors
+            if (_error == null)
+                _error = ex;
+            LOG.debug(Log.EXCEPTION, ex);
+            LOG.warn("ERROR@" + getLocationString(ex) + " : " + ex.toString());
+        }
+
+        /* ------------------------------------------------------------ */
+        public void fatalError(SAXParseException ex) throws SAXException
+        {
+            _error = ex;
+            LOG.debug(Log.EXCEPTION, ex);
+            LOG.warn("FATAL@" + getLocationString(ex) + " : " + ex.toString());
+            throw ex;
+        }
+
+        /* ------------------------------------------------------------ */
+        private String getLocationString(SAXParseException ex)
+        {
+            return ex.getSystemId() + " line:" + ex.getLineNumber() + " col:" + ex.getColumnNumber();
+        }
+
+        /* ------------------------------------------------------------ */
+        public InputSource resolveEntity(String pid, String sid)
+        {
+            if (LOG.isDebugEnabled())
+                LOG.debug("resolveEntity(" + pid + ", " + sid + ")");
+            
+            if (sid!=null && sid.endsWith(".dtd"))
+                _dtd=sid;
+            
+            URL entity = null;
+            if (pid != null)
+                entity = (URL) _redirectMap.get(pid);
+            if (entity == null)
+                entity = (URL) _redirectMap.get(sid);
+            if (entity == null)
+            {
+                String dtd = sid;
+                if (dtd.lastIndexOf('/') >= 0)
+                    dtd = dtd.substring(dtd.lastIndexOf('/') + 1);
+
+                if (LOG.isDebugEnabled())
+                    LOG.debug("Can't exact match entity in redirect map, trying " + dtd);
+                entity = (URL) _redirectMap.get(dtd);
+            }
+
+            if (entity != null)
+            {
+                try
+                {
+                    InputStream in = entity.openStream();
+                    if (LOG.isDebugEnabled())
+                        LOG.debug("Redirected entity " + sid + " --> " + entity);
+                    InputSource is = new InputSource(in);
+                    is.setSystemId(sid);
+                    return is;
+                }
+                catch (IOException e)
+                {
+                    LOG.ignore(e);
+                }
+            }
+            return null;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * XML Attribute.
+     */
+    public static class Attribute
+    {
+        private String _name;
+        private String _value;
+
+        Attribute(String n, String v)
+        {
+            _name = n;
+            _value = v;
+        }
+
+        public String getName()
+        {
+            return _name;
+        }
+
+        public String getValue()
+        {
+            return _value;
+        }
+    }
+
+    /* ------------------------------------------------------------ */
+    /* ------------------------------------------------------------ */
+    /**
+     * XML Node. Represents an XML element with optional attributes and ordered content.
+     */
+    public static class Node extends AbstractList<Object>
+    {
+        Node _parent;
+        private ArrayList<Object> _list;
+        private String _tag;
+        private Attribute[] _attrs;
+        private boolean _lastString = false;
+        private String _path;
+
+        /* ------------------------------------------------------------ */
+        Node(Node parent, String tag, Attributes attrs)
+        {
+            _parent = parent;
+            _tag = tag;
+
+            if (attrs != null)
+            {
+                _attrs = new Attribute[attrs.getLength()];
+                for (int i = 0; i < attrs.getLength(); i++)
+                {
+                    String name = attrs.getLocalName(i);
+                    if (name == null || name.equals(""))
+                        name = attrs.getQName(i);
+                    _attrs[i] = new Attribute(name, attrs.getValue(i));
+                }
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public Node getParent()
+        {
+            return _parent;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getTag()
+        {
+            return _tag;
+        }
+
+        /* ------------------------------------------------------------ */
+        public String getPath()
+        {
+            if (_path == null)
+            {
+                if (getParent() != null && getParent().getTag() != null)
+                    _path = getParent().getPath() + "/" + _tag;
+                else
+                    _path = "/" + _tag;
+            }
+            return _path;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get an array of element attributes.
+         */
+        public Attribute[] getAttributes()
+        {
+            return _attrs;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get an element attribute.
+         * 
+         * @return attribute or null.
+         */
+        public String getAttribute(String name)
+        {
+            return getAttribute(name, null);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get an element attribute.
+         * 
+         * @return attribute or null.
+         */
+        public String getAttribute(String name, String dft)
+        {
+            if (_attrs == null || name == null)
+                return dft;
+            for (int i = 0; i < _attrs.length; i++)
+                if (name.equals(_attrs[i].getName()))
+                    return _attrs[i].getValue();
+            return dft;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get the number of children nodes.
+         */
+        public int size()
+        {
+            if (_list != null)
+                return _list.size();
+            return 0;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get the ith child node or content.
+         * 
+         * @return Node or String.
+         */
+        public Object get(int i)
+        {
+            if (_list != null)
+                return _list.get(i);
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get the first child node with the tag.
+         * 
+         * @param tag
+         * @return Node or null.
+         */
+        public Node get(String tag)
+        {
+            if (_list != null)
+            {
+                for (int i = 0; i < _list.size(); i++)
+                {
+                    Object o = _list.get(i);
+                    if (o instanceof Node)
+                    {
+                        Node n = (Node) o;
+                        if (tag.equals(n._tag))
+                            return n;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /* ------------------------------------------------------------ */
+        @Override
+        public void add(int i, Object o)
+        {
+            if (_list == null)
+                _list = new ArrayList<Object>();
+            if (o instanceof String)
+            {
+                if (_lastString)
+                {
+                    int last = _list.size() - 1;
+                    _list.set(last, (String) _list.get(last) + o);
+                }
+                else
+                    _list.add(i, o);
+                _lastString = true;
+            }
+            else
+            {
+                _lastString = false;
+                _list.add(i, o);
+            }
+        }
+
+        /* ------------------------------------------------------------ */
+        public void clear()
+        {
+            if (_list != null)
+                _list.clear();
+            _list = null;
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Get a tag as a string.
+         * 
+         * @param tag The tag to get
+         * @param tags IF true, tags are included in the value.
+         * @param trim If true, trim the value.
+         * @return results of get(tag).toString(tags).
+         */
+        public String getString(String tag, boolean tags, boolean trim)
+        {
+            Node node = get(tag);
+            if (node == null)
+                return null;
+            String s = node.toString(tags);
+            if (s != null && trim)
+                s = s.trim();
+            return s;
+        }
+
+        /* ------------------------------------------------------------ */
+        public synchronized String toString()
+        {
+            return toString(true);
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Convert to a string.
+         * 
+         * @param tag If false, only _content is shown.
+         */
+        public synchronized String toString(boolean tag)
+        {
+            StringBuilder buf = new StringBuilder();
+            toString(buf, tag);
+            return buf.toString();
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Convert to a string.
+         * 
+         * @param tag If false, only _content is shown.
+         */
+        public synchronized String toString(boolean tag, boolean trim)
+        {
+            String s = toString(tag);
+            if (s != null && trim)
+                s = s.trim();
+            return s;
+        }
+
+        /* ------------------------------------------------------------ */
+        private synchronized void toString(StringBuilder buf, boolean tag)
+        {
+            if (tag)
+            {
+                buf.append("<");
+                buf.append(_tag);
+
+                if (_attrs != null)
+                {
+                    for (int i = 0; i < _attrs.length; i++)
+                    {
+                        buf.append(' ');
+                        buf.append(_attrs[i].getName());
+                        buf.append("=\"");
+                        buf.append(_attrs[i].getValue());
+                        buf.append("\"");
+                    }
+                }
+            }
+
+            if (_list != null)
+            {
+                if (tag)
+                    buf.append(">");
+                for (int i = 0; i < _list.size(); i++)
+                {
+                    Object o = _list.get(i);
+                    if (o == null)
+                        continue;
+                    if (o instanceof Node)
+                        ((Node) o).toString(buf, tag);
+                    else
+                        buf.append(o.toString());
+                }
+                if (tag)
+                {
+                    buf.append("</");
+                    buf.append(_tag);
+                    buf.append(">");
+                }
+            }
+            else if (tag)
+                buf.append("/>");
+        }
+
+        /* ------------------------------------------------------------ */
+        /**
+         * Iterator over named child nodes.
+         * 
+         * @param tag The tag of the nodes.
+         * @return Iterator over all child nodes with the specified tag.
+         */
+        public Iterator<Node> iterator(final String tag)
+        {
+            return new Iterator<Node>()
+            {
+                int c = 0;
+                Node _node;
+
+                /* -------------------------------------------------- */
+                public boolean hasNext()
+                {
+                    if (_node != null)
+                        return true;
+                    while (_list != null && c < _list.size())
+                    {
+                        Object o = _list.get(c);
+                        if (o instanceof Node)
+                        {
+                            Node n = (Node) o;
+                            if (tag.equals(n._tag))
+                            {
+                                _node = n;
+                                return true;
+                            }
+                        }
+                        c++;
+                    }
+                    return false;
+                }
+
+                /* -------------------------------------------------- */
+                public Node next()
+                {
+                    try
+                    {
+                        if (hasNext())
+                            return _node;
+                        throw new NoSuchElementException();
+                    }
+                    finally
+                    {
+                        _node = null;
+                        c++;
+                    }
+                }
+
+                /* -------------------------------------------------- */
+                public void remove()
+                {
+                    throw new UnsupportedOperationException("Not supported");
+                }
+            };
+        }
+    }
+}
diff --git a/src/resources/jetty-dir.css b/src/resources/jetty-dir.css
new file mode 100644
index 0000000..1686813
--- /dev/null
+++ b/src/resources/jetty-dir.css
@@ -0,0 +1,31 @@
+body 
+{
+	background-color: #FFFFFF;
+	margin: 10px;
+	padding: 5px;
+}
+
+h1
+{
+	text-shadow: #000000 -1px -1px 1px;
+	color: #FC390E;
+	font-weight: bold;
+}
+
+a
+{
+	color: #7036be;
+	font-weight: bold;
+	font-style: normal;
+	text-decoration: none;
+	font-size:inherit;
+}
+
+td
+{
+	font-style: italic;
+	padding: 2px 15px 2px 0px;
+}
+
+
+
diff --git a/src/resources/org/eclipse/jetty/favicon.ico b/src/resources/org/eclipse/jetty/favicon.ico
new file mode 100644
index 0000000..ea9e174
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/favicon.ico
Binary files differ
diff --git a/src/resources/org/eclipse/jetty/http/encoding.properties b/src/resources/org/eclipse/jetty/http/encoding.properties
new file mode 100644
index 0000000..311c802
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/http/encoding.properties
@@ -0,0 +1,4 @@
+text/html	= ISO-8859-1
+text/plain	= ISO-8859-1
+text/xml	= UTF-8
+text/json   = UTF-8
diff --git a/src/resources/org/eclipse/jetty/http/mime.properties b/src/resources/org/eclipse/jetty/http/mime.properties
new file mode 100644
index 0000000..8425ac1
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/http/mime.properties
@@ -0,0 +1,181 @@
+ai	= application/postscript
+aif	= audio/x-aiff
+aifc	= audio/x-aiff
+aiff	= audio/x-aiff
+apk = application/vnd.android.package-archive
+asc	= text/plain
+asf     = video/x.ms.asf
+asx     = video/x.ms.asx
+au	= audio/basic
+avi	= video/x-msvideo
+bcpio	= application/x-bcpio
+bin	= application/octet-stream
+cab     = application/x-cabinet
+cdf	= application/x-netcdf
+class	= application/java-vm
+cpio	= application/x-cpio
+cpt	= application/mac-compactpro
+crt	= application/x-x509-ca-cert
+csh	= application/x-csh
+css	= text/css
+csv     = text/comma-separated-values
+dcr	= application/x-director
+dir	= application/x-director
+dll     = application/x-msdownload
+dms	= application/octet-stream
+doc	= application/msword
+dtd     = application/xml-dtd
+dvi	= application/x-dvi
+dxr	= application/x-director
+eps	= application/postscript
+etx	= text/x-setext
+exe	= application/octet-stream
+ez	= application/andrew-inset
+gif	= image/gif
+gtar	= application/x-gtar
+gz	= application/gzip
+gzip	= application/gzip
+hdf	= application/x-hdf
+hqx	= application/mac-binhex40
+htc = text/x-component
+htm	= text/html
+html	= text/html
+ice	= x-conference/x-cooltalk
+ico	= image/x-icon
+ief	= image/ief
+iges	= model/iges
+igs	= model/iges
+jad     = text/vnd.sun.j2me.app-descriptor
+jar     = application/java-archive
+java	= text/plain
+jnlp	= application/x-java-jnlp-file
+jpe	= image/jpeg
+jpeg	= image/jpeg
+jpg	= image/jpeg
+js	= application/x-javascript
+jsp	= text/html
+kar	= audio/midi
+latex	= application/x-latex
+lha	= application/octet-stream
+lzh	= application/octet-stream
+man	= application/x-troff-man
+mathml	= application/mathml+xml
+me	= application/x-troff-me
+mesh	= model/mesh
+mid	= audio/midi
+midi	= audio/midi
+mif	= application/vnd.mif
+mol     = chemical/x-mdl-molfile
+mov	= video/quicktime
+movie	= video/x-sgi-movie
+mp2	= audio/mpeg
+mp3	= audio/mpeg
+mpe	= video/mpeg
+mpeg	= video/mpeg
+mpg	= video/mpeg
+mpga	= audio/mpeg
+ms	= application/x-troff-ms
+msh	= model/mesh
+msi	= application/octet-stream
+nc	= application/x-netcdf
+oda	= application/oda
+odb     = application/vnd.oasis.opendocument.database
+odc     = application/vnd.oasis.opendocument.chart
+odf     = application/vnd.oasis.opendocument.formula
+odg     = application/vnd.oasis.opendocument.graphics
+odi     = application/vnd.oasis.opendocument.image
+odm     = application/vnd.oasis.opendocument.text-master
+odp     = application/vnd.oasis.opendocument.presentation
+ods     = application/vnd.oasis.opendocument.spreadsheet
+odt     = application/vnd.oasis.opendocument.text
+ogg	= application/ogg
+otc     = application/vnd.oasis.opendocument.chart-template
+otf     = application/vnd.oasis.opendocument.formula-template
+otg     = application/vnd.oasis.opendocument.graphics-template
+oth     = application/vnd.oasis.opendocument.text-web
+oti     = application/vnd.oasis.opendocument.image-template
+otp     = application/vnd.oasis.opendocument.presentation-template
+ots     = application/vnd.oasis.opendocument.spreadsheet-template
+ott     = application/vnd.oasis.opendocument.text-template
+pbm	= image/x-portable-bitmap
+pdb	= chemical/x-pdb
+pdf	= application/pdf
+pgm	= image/x-portable-graymap
+pgn	= application/x-chess-pgn
+png	= image/png
+pnm	= image/x-portable-anymap
+ppm	= image/x-portable-pixmap
+pps     = application/vnd.ms-powerpoint
+ppt	= application/vnd.ms-powerpoint
+ps	= application/postscript
+qt	= video/quicktime
+ra	= audio/x-pn-realaudio
+ram	= audio/x-pn-realaudio
+ras	= image/x-cmu-raster
+rdf	= application/rdf+xml
+rgb	= image/x-rgb
+rm	= audio/x-pn-realaudio
+roff	= application/x-troff
+rpm	= application/x-rpm
+rtf	= application/rtf
+rtx	= text/richtext
+rv      = video/vnd.rn-realvideo
+ser     = application/java-serialized-object
+sgm	= text/sgml
+sgml	= text/sgml
+sh	= application/x-sh
+shar	= application/x-shar
+silo	= model/mesh
+sit	= application/x-stuffit
+skd	= application/x-koan
+skm	= application/x-koan
+skp	= application/x-koan
+skt	= application/x-koan
+smi	= application/smil
+smil	= application/smil
+snd	= audio/basic
+spl	= application/x-futuresplash
+src	= application/x-wais-source
+sv4cpio	= application/x-sv4cpio
+sv4crc	= application/x-sv4crc
+svg	= image/svg+xml
+swf	= application/x-shockwave-flash
+t	= application/x-troff
+tar	= application/x-tar
+tar.gz	= application/x-gtar
+tcl	= application/x-tcl
+tex	= application/x-tex
+texi	= application/x-texinfo
+texinfo	= application/x-texinfo
+tgz	= application/x-gtar
+tif	= image/tiff
+tiff	= image/tiff
+tr	= application/x-troff
+tsv	= text/tab-separated-values
+txt	= text/plain
+ustar	= application/x-ustar
+vcd	= application/x-cdlink
+vrml	= model/vrml
+vxml	= application/voicexml+xml
+wav	= audio/x-wav
+wbmp	= image/vnd.wap.wbmp
+wml	= text/vnd.wap.wml
+wmlc	= application/vnd.wap.wmlc
+wmls	= text/vnd.wap.wmlscript
+wmlsc	= application/vnd.wap.wmlscriptc
+wrl	= model/vrml
+wtls-ca-certificate	= application/vnd.wap.wtls-ca-certificate
+xbm	= image/x-xbitmap
+xht	= application/xhtml+xml
+xhtml	= application/xhtml+xml
+xls	= application/vnd.ms-excel
+xml	= application/xml
+xpm	= image/x-xpixmap
+xsd	= application/xml
+xsl	= application/xml
+xslt	= application/xslt+xml
+xul	= application/vnd.mozilla.xul+xml
+xwd	= image/x-xwindowdump
+xyz	= chemical/x-xyz
+z	= application/compress
+zip	= application/zip
diff --git a/src/resources/org/eclipse/jetty/http/useragents b/src/resources/org/eclipse/jetty/http/useragents
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/http/useragents
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties
new file mode 100644
index 0000000..8b82f1a
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/AbstractHandler-mbean.properties
@@ -0,0 +1 @@
+AbstractHandler: Jetty Handler.
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties
new file mode 100644
index 0000000..faf27f0
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandler-mbean.properties
@@ -0,0 +1,24 @@
+ContextHandler: URI Context
+aliases: True if alias checking is performed on resource
+allowNullPathInfo: Checks if the /context is not redirected to /context/
+classPath: RO: The file classpath
+compactPath: True if URLs are compacted to replace the multiple '/'s with a single '/'
+connectorNames: Names and ports of accepted connectors
+contextAttributes: RO:MBean: Map of context attributes
+contextPath: URI prefix of context
+displayName: RO: Display name of the Context
+errorHandler: MObject: The error handler to use for the context
+initParams: Initial Parameter map for the context
+maxFormContentSize: The maximum content size
+removeContextAttribute(java.lang.String): MBean:ACTION: remove context attribute
+removeContextAttribute(java.lang.String)[0]: name: The attribute name
+resourceBase: Document root for the context
+setContextAttribute(java.lang.String,java.lang.Object): MBean:ACTION: Set context attribute
+setContextAttribute(java.lang.String,java.lang.Object)[0]: name: The attribute name
+setContextAttribute(java.lang.String,java.lang.Object)[1]: value: The attribute value
+setContextAttribute(java.lang.String,java.lang.String): MBean:ACTION: Set context attribute
+setContextAttribute(java.lang.String,java.lang.String)[0]: name: The attribute name
+setContextAttribute(java.lang.String,java.lang.String)[1]: value: The attribute value
+shutdown: False if this context is accepting new requests. True for graceful shutdown, which allows existing requests to complete
+virtualHosts: Virtual hosts accepted by the context
+welcomeFiles: Partial URIs of directory welcome files
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties
new file mode 100644
index 0000000..4ab1fef
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/ContextHandlerCollection-mbean.properties
@@ -0,0 +1,2 @@
+ContextHandlerCollection: Context Handler Collection
+mapContexts(): Update the mapping of context path to context
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties
new file mode 100644
index 0000000..37152f5
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerCollection-mbean.properties
@@ -0,0 +1,2 @@
+HandlerCollection: Handler of multiple Handlers
+handlers: MObject:Wrapped handlers
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties
new file mode 100644
index 0000000..3395f64
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/HandlerWrapper-mbean.properties
@@ -0,0 +1,2 @@
+HandlerWrapper: Handler wrapping another Handler
+handler: MObject:Wrapped handler
diff --git a/src/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties b/src/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties
new file mode 100644
index 0000000..295f4de
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/handler/jmx/StatisticsHandler-mbean.properties
@@ -0,0 +1,28 @@
+StatisticsHandler: Request Statistics gathering
+statsOnMs: Time in milliseconds stats have been collected for.
+statsReset(): Resets statistics.
+requests: Number of requests since statsReset() called.
+requestsActive: Number of requests currently active since statsReset() called.
+requestsActiveMax: Maximum number of active requests since statsReset() called.
+requestTimeMax: Maximum time in milliseconds of request handling since statsReset() called.
+requestTimeTotal: Total time in milliseconds of all request handling since statsReset() called.
+requestTimeMean: Mean of time in milliseconds of request handling since statsReset() called.
+requestTimeStdDev: Standard deviation of time in milliseconds of request handling since statsReset() called.
+dispatched: Number of dispatches since statsReset() called.
+dispatchedActive: Number of dispatches currently active since statsReset() called.
+dispatchedActiveMax: Maximum number of active dispatches since statsReset() called.
+dispatchedTimeMax: Maximum time in milliseconds of dispatched handling since statsReset() called.
+dispatchedTimeTotal: Total time in milliseconds of all dispatched handling since statsReset() called.
+dispatchedTimeMean: Mean of time in milliseconds of dispatch handling since statsReset() called.
+dispatchedTimeStdDev: Standard deviation of time in milliseconds of dispatch handling since statsReset() called.
+suspends: Number of requests suspended since statsReset() called.
+suspendsActive: Number of dispatches currently active since statsReset() called.
+suspendsActiveMax: Maximum number of active dispatches since statsReset() called.
+resumes: Number of requests resumed since statsReset() called.
+expires: Number of requests expired since statsReset() called.
+responses1xx: Number of responses with a 1xx status since statsReset() called.
+responses2xx: Number of responses with a 2xx status since statsReset() called.
+responses3xx: Number of responses with a 3xx status since statsReset() called.
+responses4xx: Number of responses with a 4xx status since statsReset() called.
+responses5xx: Number of responses with a 5xx status since statsReset() called.
+responsesBytesTotal: Total number of bytes of all responses since statsReset() called.
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties
new file mode 100644
index 0000000..fd76672
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/AbstractConnector-mbean.properties
@@ -0,0 +1,19 @@
+AbstractConnector: Abstract implementation of the Connector interface.
+acceptors: The number of acceptor threads.
+acceptQueueSize: The size of the accept queue.
+acceptorPriorityOffset: Priority offset of the acceptor threads. The priority is adjusted by this amount to either favor the acceptance of new threads and newly active connections or to favor the handling of already dispatched connections.
+forwardedForHeader: The header name for forwarded for (default x-forwarded-for). 
+forwardedHostHeader: The header name for forwarded hosts (default x-forwarded-host)
+forwardedServerHeader: The header name for forwarded server (default x-forwarded-server)
+forwarded: Whether reverse proxy handling is on. True if this connector is checking the forwarded for/host/server headers.
+host: Host name of the server.
+hostHeader: Forced value for the host header. Only used if forwarded is true.
+soLingerTime: Enable or disable SO_LINGER with the specified linger time in seconds.
+reuseAddress: Whether the server socket will be opened in SO_REUSEADDR mode.
+name: Name of the connector.
+resolveNames: Whether or not to use DNS when handling forwards. 
+confidentialPort: Port to use for confidential redirections.
+confidentialScheme: Scheme to use for confidential redirections.
+integralPort: Port to use for integral redirections.
+integralScheme: Scheme to use for integral redirections.
+lowResourcesMaxIdleTime: The period in ms that a connection may be idle when the connector has low resources, before it is closed.
diff --git a/src/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties
new file mode 100644
index 0000000..efd5cef
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/Connector-mbean.properties
@@ -0,0 +1,29 @@
+Connector: HTTP Connector.
+server: MObject:RO:The server for this connector
+requestHeaderSize: The size of a request header buffer
+requestBufferSize: The size of a request content buffer
+responseHeaderSize: The size of a response header buffer
+responseBufferSize: The size of a response content buffer
+integralPort: Port to use for integral redirections
+integralScheme: Scheme to use for integral redirections
+confidentialPort: Port to use for confidential redirections
+confidentialScheme: Scheme to use for confidential redirections
+host: Host name to accept connections on
+port: TCP/IP port to accept connections on
+maxIdleTime: Maximum time in ms that a connection can be idle before being closed
+statsOn: True if statistics collection is turned on.
+statsOnMs: Time in milliseconds stats have been collected for. 
+statsReset(): Reset statistics.
+connections: Number of connections accepted by the server since statsReset() called. Undefined if setStatsOn(false).
+connectionsOpen: Number of connections currently open that were opened since statsReset() called. Undefined if setStatsOn(false).
+connectionsOpenMax: Maximum number of connections opened simultaneously since statsReset() called. Undefined if setStatsOn(false).
+connectionsDurationMean: Mean duration in milliseconds of open connections since statsReset() called. Undefined if setStatsOn(false).
+connectionsDurationStdDev: Standard deviation of duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
+connectionsDurationMax: Maximum duration in milliseconds of an open connection since statsReset() called. Undefined if setStatsOn(false).
+connectionsDurationTotal: Total duration in milliseconds of all open connection since statsReset() called. Undefined if setStatsOn(false).
+connectionsRequestsMean: Mean number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+connectionsRequestsStdDev: Standard deviation of number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+connectionsRequestsMax: Maximum number of requests per connection since statsReset() called. Undefined if setStatsOn(false).
+requests: Number of requests since statsReset() called. Undefined if setStatsOn(false).
+open(): Open the listening port
+close(): Close the listening port (but allow existing connections to continue for graceful shutdown)
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties
new file mode 100644
index 0000000..eea633f
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/Handler-mbean.properties
@@ -0,0 +1,3 @@
+Handler: Jetty Handler.
+server: MObject:RO:The Jetty server for this handler
+destroy(): destroy associated resources (eg MBean)
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties
new file mode 100644
index 0000000..374e020
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/HandlerContainer-mbean.properties
@@ -0,0 +1,3 @@
+HandlerContainer: Handler of multiple Handlers
+handlers: MObject:RO:Handlers in this container
+childHandlers: MObject:RO:All contained handlers
diff --git a/src/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties
new file mode 100644
index 0000000..0c0e340
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/NCSARequestLog-mbean.properties
@@ -0,0 +1,6 @@
+NCSARequestLog : NCSA standard format request log
+filename : Filename of log
+retainDays : Number of days that the log files are kept
+append : Existing log files are appended to the new one
+extended : Use the extended NCSA format
+LogTimeZone : The timezone
diff --git a/src/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties b/src/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties
new file mode 100644
index 0000000..017711c
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/jmx/Server-mbean.properties
@@ -0,0 +1,9 @@
+Server: Jetty HTTP Servlet server
+connectors: MObject:HTTP Connectors for this server
+version: RO: The version of this server
+sendServerVersion: If true include the server version in HTTP headers
+threadPool: MObject:The server Thread Pool
+contexts: MMBean:RO:The contexts of this server
+startupTime: MBean:RO:The startup time, in milliseconds, since January 1st 1970
+dumpAfterStart: RW:Dump state to stderr after start
+dumpBeforeStop: RW:Dump state to stderr before stop
diff --git a/src/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties b/src/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties
new file mode 100644
index 0000000..dbe718d
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/nio/jmx/SelectChannelConnector-mbean.properties
@@ -0,0 +1,2 @@
+SelectChannelConnector: HTTP connector using NIO ByteChannels and Selectors
+lowResourcesConnections: The number of connections, which if exceeded represents low resources
diff --git a/src/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties b/src/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties
new file mode 100644
index 0000000..92deb5f
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/server/session/jmx/AbstractSessionManager-mbean.properties
@@ -0,0 +1,19 @@
+AbstractSessionManager: Abstract Session Manager
+httpOnly: True if cookies use the http only flag
+idManager: MObject:RO:The ID Manager instance
+maxCookieAge: if greater than zero, the time in seconds a session cookie will last for
+maxInactiveInterval: default maximum time in seconds a session may be idle
+refreshCookieAge: The time in seconds after which a session cookie is re-set
+secureCookies: If true, the secure cookie flag is set on session cookies
+sessionCookie: The set session cookie 
+sessionDomain: The domain of the session cookie or null for the default
+sessionPath: The path of the session cookie or null for the default
+sessionsTotal: The total number of sessions
+sessionIdPathParameterName: The name to use for URL session tracking
+statsReset(): Reset statistics
+sessions: Current instantaneous number of sessions
+sessionsMax: Maximum number of simultaneous sessions since statsReset() was called
+sessionTimeMax: Maximum amount of time in seconds session remained valid since statsReset() was called
+sessionTimeTotal: Total amount of time in seconds sessions remained valid since statsReset() was called
+sessionTimeMean: Mean amount of time in seconds  a session remained valid since statsReset() was called
+sessionTimeStdDev: Standard deviation of amount of time in seconds  a session remained valid since statsReset() was called
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties
new file mode 100644
index 0000000..afcbe2f
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/FilterMapping-mbean.properties
@@ -0,0 +1,4 @@
+FilterMapping: Filter Mapping
+filterName: RO:Filter Name
+pathSpecs: RO:URL patterns
+servletNames: RO:Servlet Names
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties
new file mode 100644
index 0000000..7730ff4
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/Holder-mbean.properties
@@ -0,0 +1,5 @@
+Holder: Holder
+name: RO:Name
+displayName: RO:Display Name
+className: RO:Class Name
+initParameters: RO:Initial parameters
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties
new file mode 100644
index 0000000..2c1a05a
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/ServletContextHandler-mbean.properties
@@ -0,0 +1,4 @@
+ServletContextHandler: Servlet Context Handler
+securityHandler: MObject: The context's security handler
+servletHandler: MObject: The context's servlet handler
+sessionHandler: MObject: The context's session handler
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties
new file mode 100644
index 0000000..9326c19
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/ServletHandler-mbean.properties
@@ -0,0 +1,5 @@
+ServletHandler: Servlet Handler
+servlets: MObject:RO:Servlets
+servletMappings: MObject:RO:Mappings of servlets
+filters: MObject:RO:Filters
+filterMappings: MObject:RO:Mappings of filters
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties
new file mode 100644
index 0000000..3844546
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/ServletHolder-mbean.properties
@@ -0,0 +1,4 @@
+ServletHolder: Servlet Holder
+initOrder: Initialization order
+runAsRole: Role to run servlet as
+forcedPath: Forced servlet path
diff --git a/src/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties b/src/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties
new file mode 100644
index 0000000..efe07ff
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlet/jmx/ServletMapping-mbean.properties
@@ -0,0 +1,3 @@
+ServletMapping: Servlet Mapping
+servletName: RO:Servlet Name
+pathSpecs: RO:URL patterns
diff --git a/src/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties b/src/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties
new file mode 100644
index 0000000..9523d23
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlets/jmx/DoSFilter-mbean.properties
@@ -0,0 +1,18 @@
+DoSFilter: Limit exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client.
+maxRequestsPerSec: maximum number of requests from a connection per second. Requests in excess of this are first delayed, then throttled.
+delayMs: delay (in milliseconds) that is applied to all requests over the rate limit, before they are considered at all, 0 - no delay, -1 - reject request.
+maxWaitMs: maximum amount of time (in milliseconds) the filter will blocking wait for the throttle semaphore.
+throttledRequests: number of requests over the rate limit able to be considered at once.
+throttleMs: amount of time (in milliseconds) to async wait for semaphore.
+maxRequestMs: maximum amount of time (in milliseconds) to allow the request to process.
+maxIdleTrackerMs: maximum amount of time (in milliseconds) to keep track of request rates for a connection, before deciding that the user has gone away, and discarding it.
+insertHeaders: insert the DoSFilter headers into the response.
+trackSessions: usage rate is tracked by session if a session exists.
+remotePort: usage rate is tracked by IP+port (effectively connection) if session tracking is not used.
+enabled: whether this filter is enabled
+whitelist: comma separated list of IP addresses that will not be rate limited.
+clearWhitelist(): clears the list of IP addresses that will not be rate limited.
+addWhitelistAddress(java.lang.String):ACTION: adds an IP address that will not be rate limited.
+addWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.
+removeWhitelistAddress(java.lang.String):ACTION: removes an IP address that will not be rate limited.
+removeWhitelistAddress(java.lang.String)[0]:address: the IP address that will not be rate limited.
diff --git a/src/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties b/src/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties
new file mode 100644
index 0000000..c781d63
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/servlets/jmx/QoSFilter-mbean.properties
@@ -0,0 +1,4 @@
+QoSFilter: Quality of Service Filter.
+maxRequests: maximum number of requests allowed to be processedat the same time.
+waitMs: (short) amount of time (in milliseconds) that the filter would wait for the semaphore to become available before suspending a request.
+suspendMs: amount of time (in milliseconds) that the filter would suspend a request for while waiting for the semaphore to become available.
diff --git a/src/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties b/src/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties
new file mode 100644
index 0000000..437f426
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/component/jmx/AggregateLifeCycle-mbean.properties
@@ -0,0 +1,2 @@
+AggregateLifeCycle: A LifeCycle holding other LifeCycles
+dumpStdErr():Object:INFO:Dump the nested Object state to StdErr
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties b/src/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties
new file mode 100644
index 0000000..6015f73
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/component/jmx/Dumpable-mbean.properties
@@ -0,0 +1,2 @@
+Dumpable: Dumpable Object
+dump():Object:INFO:Dump the nested Object state as a String
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties b/src/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties
new file mode 100644
index 0000000..0c0ab61
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/component/jmx/LifeCycle-mbean.properties
@@ -0,0 +1,9 @@
+LifeCycle: Startable object
+start(): Starts the instance
+stop(): Stops the instance
+running: Instance is started or starting
+started: Instance is started
+starting: Instance is starting
+stopping: Instance is stopping
+stopped: Instance is stopped
+failed: Instance is failed
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties b/src/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties
new file mode 100644
index 0000000..18abcb1
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/log/jmx/Log-mbean.properties
@@ -0,0 +1,8 @@
+Log: Jetty Logging implementaton
+loggers:MBean: List of all instantiated loggers
+debugEnabled:RW: True if debug enabled for root logger Log.LOG
+isDebugEnabled(java.lang.String):MBean:INFO: True if debug is enabled for the given logger
+isDebugEnabled(java.lang.String)[0]:loggerName: Name of the logger to return isDebugEnabled for
+setDebugEnabled(java.lang.String,java.lang.Boolean):MBean:ACTION: Set debug enabled for the given logger
+setDebugEnabled(java.lang.String,java.lang.Boolean)[0]:loggerName: Name of the logger to set debug enabled
+setDebugEnabled(java.lang.String,java.lang.Boolean)[1]:enabled: true to enable debug, false otherwise
diff --git a/src/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties b/src/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties
new file mode 100644
index 0000000..9bdffd8
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/thread/jmx/QueuedThreadPool-mbean.properties
@@ -0,0 +1,16 @@
+QueuedThreadPool: A thread pool with no max bound by default
+minThreads: Minimum number of threads in the pool
+maxThreads: Maximum number threads in the pool
+maxQueued: The maximum size of the job queue or -1 for unbounded.
+name: Name of the thread pool
+daemon: Is pool thread using daemon thread
+threadsPriority: The priority of threads in the pool
+maxIdleTimeMs: Maximum time a thread may be idle in ms
+detailedDump: Full stack detail in dump output
+dump(): Dump thread state
+stopThread(long): Stop a pool thread
+stopThread(long)[0]: id:Thread ID
+interruptThread(long): Interrupt a pool thread
+interruptThread(long)[0]: id:Thread ID
+dumpThread(long): Dump a pool thread stack
+dumpThread(long)[0]: id:Thread ID
diff --git a/src/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties b/src/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties
new file mode 100644
index 0000000..9ee55bf
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/util/thread/jmx/ThreadPool-mbean.properties
@@ -0,0 +1,4 @@
+ThreadPool: Pool of threads
+threads: RO:Number of Threads in pool
+idleThreads: RO:Number of idle Threads in pool
+lowOnThreads: RO:Indicates the pool is low on available threads.
diff --git a/src/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties b/src/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties
new file mode 100644
index 0000000..5a09672
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/webapp/jmx/WebAppContext-mbean.properties
@@ -0,0 +1,14 @@
+WebAppContext: Web Application ContextHandler
+configurationClasses: Array of names of configuration classes 
+defaultsDescriptor: Default web.xml descriptor applied before standard web.xml
+overrideDescriptor: Override web.xml descriptor applied after standard web.xml
+serverClasses: Classes and packages hidden by the context classloader
+systemClasses: Classes and packages given priority by context classloader
+war: WAR file location
+distributable: Is the web application distributable
+extractWAR: Is the war file extraced on deploy
+copyWebDir: Is the web application directory copied on deploy
+parentLoaderPriority: Is the parent classloader given priority
+descriptor: The standard web.xml descriptor
+extraClasspath: Extra classpath for the context classloader
+tempDirectory: Temporary directory location
\ No newline at end of file
diff --git a/src/resources/org/eclipse/jetty/xml/configure_6_0.dtd b/src/resources/org/eclipse/jetty/xml/configure_6_0.dtd
new file mode 100644
index 0000000..f79d4b7
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/xml/configure_6_0.dtd
@@ -0,0 +1,265 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+This is the document type descriptor for the
+org.eclipse.XmlConfiguration class.  It allows a java object to be
+configured by with a sequence of Set, Put and Call elements.  These tags are
+mapped to methods on the object to be configured as follows:
+
+  <Set  name="Test">value</Set>              ==  obj.setTest("value");
+  <Put  name="Test">value</Put>              ==  obj.put("Test","value");
+  <Call name="test"><Arg>value</Arg></Call>  ==  obj.test("value");
+
+Values themselves may be configured objects that are created with the
+<New> tag or returned from a <Call> tag.
+
+Values are matched to arguments on a best effort approach, but types
+my be specified if a match is not achieved.
+
+-->
+
+<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
+<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Property">
+
+<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
+<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
+<!ENTITY % CLASSATTR "class NMTOKEN #REQUIRED" >
+<!ENTITY % NAMEATTR "name NMTOKEN #REQUIRED" >
+<!ENTITY % IMPLIEDNAMEATTR "name NMTOKEN #IMPLIED" >
+<!ENTITY % DEFAULTATTR "default CDATA #IMPLIED" >
+<!ENTITY % IDATTR "id NMTOKEN #IMPLIED" >
+<!ENTITY % REQUIREDIDATTR "id NMTOKEN #REQUIRED" >
+
+
+<!--
+Configure Element.
+This is the root element that specifies the class of object that
+can be configured:
+
+    <Configure class="com.acme.MyClass"> ... </Configure>
+-->
+<!ELEMENT Configure (%CONFIG;)* >
+<!ATTLIST Configure %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Set Element.
+This element maps to a call to a setter method or field on the current object.
+The name and optional type attributes are used to select the setter
+method. If the name given is xxx, then a setXxx method is used, or
+the xxx field is used of setXxx cannot be found.
+A Set element can contain value text and/or the value objects returned
+by other elements such as Call, New, SystemProperty, etc.
+If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+
+A Set with a class attribute is treated as a static set method invocation.
+-->
+<!ELEMENT Set ( %VALUE; )* >
+<!ATTLIST Set %NAMEATTR; %TYPEATTR; %IMPLIEDCLASSATTR; >
+
+
+<!--
+Get Element.
+This element maps to a call to a getter method or field on the current object.
+The name attribute is used to select the get method.
+If the name given is xxx, then a getXxx method is used, or
+the xxx field is used if getXxx cannot be found.
+A Get element can contain other elements such as Set, Put, Call, etc.
+which act on the object returned by the get call.
+
+A Get with a class attribute is treated as a static get method or field.
+-->
+<!ELEMENT Get (%CONFIG;)*>
+<!ATTLIST Get %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Put Element.
+This element maps to a call to a put method on the current object,
+which must implement the Map interface. The name attribute is used
+as the put key and the optional type attribute can force the type
+of the value.
+
+A Put element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Put ( %VALUE; )* >
+<!ATTLIST Put %NAMEATTR; %TYPEATTR; >
+
+
+<!--
+Call Element.
+This element maps to an arbitrary call to a method on the current object,
+The name attribute and Arg elements are used to select the method.
+
+A Call element can contain a sequence of Arg elements followed by
+a sequence of other elements such as Set, Put, Call, etc. which act on any object
+returned by the original call:
+
+ <Call id="o2" name="test">
+   <Arg>value1</Arg>
+   <Set name="Test">Value2</Set>
+ </Call>
+
+This is equivalent to:
+
+ Object o2 = o1.test("value1");
+ o2.setTest("value2");
+
+A Call with a class attribute is treated as a static call.
+-->
+<!ELEMENT Call (Arg*,(%CONFIG;)*)>
+<!ATTLIST Call %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR;>
+
+
+<!--
+Arg Element.
+This element defines a positional argument for the Call element.
+The optional type attribute can force the type of the value.
+
+An Arg element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Arg ( %VALUE; )* >
+<!ATTLIST Arg %TYPEATTR; %IMPLIEDNAMEATTR;  >
+
+
+<!--
+New Element.
+This element allows the creation of a new object as part of a
+value for elements such as Set, Put, Arg, etc. The class attribute determines
+the type of the new object and the contained Arg elements
+are used to select the constructor for the new object.
+
+A New element can contain a sequence of Arg elements followed by
+a sequence of elements such as Set, Put, Call, etc. elements
+which act on the new object:
+
+ <New id="o" class="com.acme.MyClass">
+   <Arg>value1</Arg>
+   <Set name="test">Value2</Set>
+ </New>
+
+This is equivalent to:
+
+ Object o = new com.acme.MyClass("value1");
+ o.setTest("Value2");
+-->
+<!ELEMENT New (Arg*,(%CONFIG;)*)>
+<!ATTLIST New %CLASSATTR; %IDATTR;>
+
+
+<!--
+Ref Element.
+This element allows a previously created object to be referenced by id.
+A Ref element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the referenced object:
+
+ <Ref id="myobject">
+   <Set name="Test">Value2</Set>
+ </New>
+-->
+<!ELEMENT Ref ((%CONFIG;)*)>
+<!ATTLIST Ref %REQUIREDIDATTR;>
+
+
+<!--
+Array Element.
+This element allows the creation of a new array as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Array type="java.lang.String">
+   <Item>value0</Item>
+   <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+ </Array>
+
+This is equivalent to:
+ String[] a = new String[] { "value0", new String("value1") };
+-->
+<!ELEMENT Array (Item*)>
+<!ATTLIST Array %TYPEATTR; %IDATTR; >
+
+
+<!--
+Map Element.
+This element allows the creation of a new map as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Map>
+   <Entry>
+     <Item>keyName</Item>
+     <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+   </Entry>
+ </Map>
+
+This is equivalent to:
+ Map m = new HashMap();
+ m.put("keyName", new String("value1"));
+-->
+<!ELEMENT Map (Entry*)>
+<!ATTLIST Map %IDATTR; >
+<!ELEMENT Entry (Item,Item)>
+
+
+<!--
+Item Element.
+This element defines an entry for the Array or Map Entry elements.
+The optional type attribute can force the type of the value.
+
+An Item element can contain value text and/or the value object of
+elements such as Call, New, SystemProperty, etc. If no value type
+is specified, then white space is trimmed out of the value.
+If it contains multiple value elements they are added as strings
+before being converted to any specified type.
+-->
+<!ELEMENT Item ( %VALUE; )* >
+<!ATTLIST Item %TYPEATTR; %IDATTR; >
+
+
+<!--
+System Property Element.
+This element allows JVM System properties to be retrieved as
+part of the value of elements such as Set, Put, Arg, etc.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+ <SystemProperty name="Test" default="value" />
+
+This is equivalent to:
+
+ System.getProperty("Test","value");
+-->
+<!ELEMENT SystemProperty EMPTY>
+<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
+
+
+<!--
+Property Element.
+This element allows arbitrary properties to be retrieved by name.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+A Property element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the retrieved object:
+
+ <Property name="Server">
+   <Call id="jdbcIdMgr" name="getAttribute">
+     <Arg>jdbcIdMgr</Arg>
+   </Call>
+ </Property>
+-->
+<!ELEMENT Property ((%CONFIG;)*)>
+<!ATTLIST Property %NAMEATTR; %DEFAULTATTR; %IDATTR;>
diff --git a/src/resources/org/eclipse/jetty/xml/configure_7_6.dtd b/src/resources/org/eclipse/jetty/xml/configure_7_6.dtd
new file mode 100644
index 0000000..04bf422
--- /dev/null
+++ b/src/resources/org/eclipse/jetty/xml/configure_7_6.dtd
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!--
+This is the document type descriptor for the
+org.eclipse.XmlConfiguration class.  It allows a java object to be
+configured by with a sequence of Set, Put and Call elements.  These tags are
+mapped to methods on the object to be configured as follows:
+
+  <Set  name="Test">value</Set>              ==  obj.setTest("value");
+  <Put  name="Test">value</Put>              ==  obj.put("Test","value");
+  <Call name="test"><Arg>value</Arg></Call>  ==  obj.test("value");
+
+Values themselves may be configured objects that are created with the
+<New> tag or returned from a <Call> tag.
+
+Values are matched to arguments on a best effort approach, but types
+my be specified if a match is not achieved.
+
+-->
+
+<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
+<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Env|Property">
+
+<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
+<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
+<!ENTITY % CLASSATTR "class NMTOKEN #REQUIRED" >
+<!ENTITY % NAMEATTR "name NMTOKEN #REQUIRED" >
+<!ENTITY % IMPLIEDNAMEATTR "name NMTOKEN #IMPLIED" >
+<!ENTITY % DEFAULTATTR "default CDATA #IMPLIED" >
+<!ENTITY % IDATTR "id NMTOKEN #IMPLIED" >
+<!ENTITY % REQUIREDIDATTR "id NMTOKEN #REQUIRED" >
+
+
+<!--
+Configure Element.
+This is the root element that specifies the class of object that
+can be configured:
+
+    <Configure class="com.acme.MyClass"> ... </Configure>
+-->
+<!ELEMENT Configure (%CONFIG;)* >
+<!ATTLIST Configure %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Set Element.
+This element maps to a call to a setter method or field on the current object.
+The name and optional type attributes are used to select the setter
+method. If the name given is xxx, then a setXxx method is used, or
+the xxx field is used of setXxx cannot be found.
+A Set element can contain value text and/or the value objects returned
+by other elements such as Call, New, SystemProperty, etc.
+If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+
+A Set with a class attribute is treated as a static set method invocation.
+-->
+<!ELEMENT Set ( %VALUE; )* >
+<!ATTLIST Set %NAMEATTR; %TYPEATTR; %IMPLIEDCLASSATTR; >
+
+
+<!--
+Get Element.
+This element maps to a call to a getter method or field on the current object.
+The name attribute is used to select the get method.
+If the name given is xxx, then a getXxx method is used, or
+the xxx field is used if getXxx cannot be found.
+A Get element can contain other elements such as Set, Put, Call, etc.
+which act on the object returned by the get call.
+
+A Get with a class attribute is treated as a static get method or field.
+-->
+<!ELEMENT Get (%CONFIG;)*>
+<!ATTLIST Get %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
+
+
+<!--
+Put Element.
+This element maps to a call to a put method on the current object,
+which must implement the Map interface. The name attribute is used
+as the put key and the optional type attribute can force the type
+of the value.
+
+A Put element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Put ( %VALUE; )* >
+<!ATTLIST Put %NAMEATTR; %TYPEATTR; >
+
+
+<!--
+Call Element.
+This element maps to an arbitrary call to a method on the current object,
+The name attribute and Arg elements are used to select the method.
+
+A Call element can contain a sequence of Arg elements followed by
+a sequence of other elements such as Set, Put, Call, etc. which act on any object
+returned by the original call:
+
+ <Call id="o2" name="test">
+   <Arg>value1</Arg>
+   <Set name="Test">Value2</Set>
+ </Call>
+
+This is equivalent to:
+
+ Object o2 = o1.test("value1");
+ o2.setTest("value2");
+
+A Call with a class attribute is treated as a static call.
+-->
+<!ELEMENT Call (Arg*,(%CONFIG;)*)>
+<!ATTLIST Call %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR;>
+
+
+<!--
+Arg Element.
+This element defines a positional argument for the Call element.
+The optional type attribute can force the type of the value.
+
+An Arg element can contain value text and/or value elements such as Call,
+New, SystemProperty, etc. If no value type is specified, then white
+space is trimmed out of the value. If it contains multiple value
+elements they are added as strings before being converted to any
+specified type.
+-->
+<!ELEMENT Arg ( %VALUE; )* >
+<!ATTLIST Arg %TYPEATTR; %IMPLIEDNAMEATTR;  >
+
+
+<!--
+New Element.
+This element allows the creation of a new object as part of a
+value for elements such as Set, Put, Arg, etc. The class attribute determines
+the type of the new object and the contained Arg elements
+are used to select the constructor for the new object.
+
+A New element can contain a sequence of Arg elements followed by
+a sequence of elements such as Set, Put, Call, etc. elements
+which act on the new object:
+
+ <New id="o" class="com.acme.MyClass">
+   <Arg>value1</Arg>
+   <Set name="test">Value2</Set>
+ </New>
+
+This is equivalent to:
+
+ Object o = new com.acme.MyClass("value1");
+ o.setTest("Value2");
+-->
+<!ELEMENT New (Arg*,(%CONFIG;)*)>
+<!ATTLIST New %CLASSATTR; %IDATTR;>
+
+
+<!--
+Ref Element.
+This element allows a previously created object to be referenced by id.
+A Ref element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the referenced object:
+
+ <Ref id="myobject">
+   <Set name="Test">Value2</Set>
+ </New>
+-->
+<!ELEMENT Ref ((%CONFIG;)*)>
+<!ATTLIST Ref %REQUIREDIDATTR;>
+
+
+<!--
+Array Element.
+This element allows the creation of a new array as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Array type="java.lang.String">
+   <Item>value0</Item>
+   <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+ </Array>
+
+This is equivalent to:
+ String[] a = new String[] { "value0", new String("value1") };
+-->
+<!ELEMENT Array (Item*)>
+<!ATTLIST Array %TYPEATTR; %IDATTR; >
+
+
+<!--
+Map Element.
+This element allows the creation of a new map as part of a
+value of elements such as Set, Put, Arg, etc. The type attribute determines
+the type of the new array and the contained Item elements
+are used for each element of the array:
+
+ <Map>
+   <Entry>
+     <Item>keyName</Item>
+     <Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
+   </Entry>
+ </Map>
+
+This is equivalent to:
+ Map m = new HashMap();
+ m.put("keyName", new String("value1"));
+-->
+<!ELEMENT Map (Entry*)>
+<!ATTLIST Map %IDATTR; >
+<!ELEMENT Entry (Item,Item)>
+
+
+<!--
+Item Element.
+This element defines an entry for the Array or Map Entry elements.
+The optional type attribute can force the type of the value.
+
+An Item element can contain value text and/or the value object of
+elements such as Call, New, SystemProperty, etc. If no value type
+is specified, then white space is trimmed out of the value.
+If it contains multiple value elements they are added as strings
+before being converted to any specified type.
+-->
+<!ELEMENT Item ( %VALUE; )* >
+<!ATTLIST Item %TYPEATTR; %IDATTR; >
+
+
+<!--
+System Property Element.
+This element allows JVM System properties to be retrieved as
+part of the value of elements such as Set, Put, Arg, etc.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+ <SystemProperty name="Test" default="value" />
+
+This is equivalent to:
+
+ System.getProperty("Test","value");
+-->
+<!ELEMENT SystemProperty EMPTY>
+<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
+
+
+<!--
+Environment variable Element.
+This element allows OS Environment variables to be retrieved as
+part of the value of elements such as Set, Put, Arg, etc.
+The name attribute specifies the env variable name and the optional
+default argument provides a default value.
+
+ <Env name="Test" default="value" />
+
+This is equivalent to:
+
+ String v=System.getEnv("Test");
+ if (v==null) v="value";
+
+-->
+<!ELEMENT Env EMPTY>
+<!ATTLIST Env %NAMEATTR; %DEFAULTATTR; %IDATTR;>
+
+
+<!--
+Property Element.
+This element allows arbitrary properties to be retrieved by name.
+The name attribute specifies the property name and the optional
+default argument provides a default value.
+
+A Property element can contain a sequence of elements such as Set, Put, Call, etc.
+which act on the retrieved object:
+
+ <Property name="Server">
+   <Call id="jdbcIdMgr" name="getAttribute">
+     <Arg>jdbcIdMgr</Arg>
+   </Call>
+ </Property>
+-->
+<!ELEMENT Property ((%CONFIG;)*)>
+<!ATTLIST Property %NAMEATTR; %DEFAULTATTR; %IDATTR;>